diff options
Diffstat (limited to 'backends')
-rw-r--r-- | backends/cxxrtl/cxxrtl.h | 229 | ||||
-rw-r--r-- | backends/cxxrtl/cxxrtl_backend.cc | 20 | ||||
-rw-r--r-- | backends/cxxrtl/cxxrtl_capi.cc | 17 | ||||
-rw-r--r-- | backends/cxxrtl/cxxrtl_capi.h | 36 | ||||
-rw-r--r-- | backends/cxxrtl/cxxrtl_vcd.h | 33 |
5 files changed, 264 insertions, 71 deletions
diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index 10184bb3f..c510b33d7 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -17,6 +17,11 @@ */ // This file is included by the designs generated with `write_cxxrtl`. It is not used in Yosys itself. +// +// The CXXRTL support library implements compile time specialized arbitrary width arithmetics, as well as provides +// composite lvalues made out of bit slices and concatenations of lvalues. This allows the `write_cxxrtl` pass +// to perform a straightforward translation of RTLIL structures to readable C++, relying on the C++ compiler +// to unwrap the abstraction and generate efficient code. #ifndef CXXRTL_H #define CXXRTL_H @@ -35,10 +40,19 @@ #include <backends/cxxrtl/cxxrtl_capi.h> -// The CXXRTL support library implements compile time specialized arbitrary width arithmetics, as well as provides -// composite lvalues made out of bit slices and concatenations of lvalues. This allows the `write_cxxrtl` pass -// to perform a straightforward translation of RTLIL structures to readable C++, relying on the C++ compiler -// to unwrap the abstraction and generate efficient code. +// CXXRTL essentially uses the C++ compiler as a hygienic macro engine that feeds an instruction selector. +// It generates a lot of specialized template functions with relatively large bodies that, when inlined +// into the caller and (for those with loops) unrolled, often expose many new optimization opportunities. +// Because of this, most of the CXXRTL runtime must be always inlined for best performance. +#ifndef __has_attribute +# define __has_attribute(x) 0 +#endif +#if __has_attribute(always_inline) +#define CXXRTL_ALWAYS_INLINE inline __attribute__((__always_inline__)) +#else +#define CXXRTL_ALWAYS_INLINE inline +#endif + namespace cxxrtl { // All arbitrary-width values in CXXRTL are backed by arrays of unsigned integers called chunks. The chunk size @@ -85,6 +99,7 @@ struct value : public expr_base<value<Bits>> { value<Bits> &operator=(const value<Bits> &) = default; // A (no-op) helper that forces the cast to value<>. + CXXRTL_ALWAYS_INLINE const value<Bits> &val() const { return *this; } @@ -101,6 +116,7 @@ struct value : public expr_base<value<Bits>> { // The trunc, zext and sext operations add or remove most significant bits (i.e. on the left); // the rtrunc and rzext operations add or remove least significant bits (i.e. on the right). template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> trunc() const { static_assert(NewBits <= Bits, "trunc() may not increase width"); value<NewBits> result; @@ -111,6 +127,7 @@ struct value : public expr_base<value<Bits>> { } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> zext() const { static_assert(NewBits >= Bits, "zext() may not decrease width"); value<NewBits> result; @@ -120,6 +137,7 @@ struct value : public expr_base<value<Bits>> { } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> sext() const { static_assert(NewBits >= Bits, "sext() may not decrease width"); value<NewBits> result; @@ -135,6 +153,7 @@ struct value : public expr_base<value<Bits>> { } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> rtrunc() const { static_assert(NewBits <= Bits, "rtrunc() may not increase width"); value<NewBits> result; @@ -154,6 +173,7 @@ struct value : public expr_base<value<Bits>> { } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> rzext() const { static_assert(NewBits >= Bits, "rzext() may not decrease width"); value<NewBits> result; @@ -172,6 +192,7 @@ struct value : public expr_base<value<Bits>> { // Bit blit operation, i.e. a partial read-modify-write. template<size_t Stop, size_t Start> + CXXRTL_ALWAYS_INLINE value<Bits> blit(const value<Stop - Start + 1> &source) const { static_assert(Stop >= Start, "blit() may not reverse bit order"); constexpr chunk::type start_mask = ~(chunk::mask << (Start % chunk::bits)); @@ -196,6 +217,7 @@ struct value : public expr_base<value<Bits>> { // than the operand. In C++17 these can be replaced with `if constexpr`. template<size_t NewBits, typename = void> struct zext_cast { + CXXRTL_ALWAYS_INLINE value<NewBits> operator()(const value<Bits> &val) { return val.template zext<NewBits>(); } @@ -203,6 +225,7 @@ struct value : public expr_base<value<Bits>> { template<size_t NewBits> struct zext_cast<NewBits, typename std::enable_if<(NewBits < Bits)>::type> { + CXXRTL_ALWAYS_INLINE value<NewBits> operator()(const value<Bits> &val) { return val.template trunc<NewBits>(); } @@ -210,6 +233,7 @@ struct value : public expr_base<value<Bits>> { template<size_t NewBits, typename = void> struct sext_cast { + CXXRTL_ALWAYS_INLINE value<NewBits> operator()(const value<Bits> &val) { return val.template sext<NewBits>(); } @@ -217,17 +241,20 @@ struct value : public expr_base<value<Bits>> { template<size_t NewBits> struct sext_cast<NewBits, typename std::enable_if<(NewBits < Bits)>::type> { + CXXRTL_ALWAYS_INLINE value<NewBits> operator()(const value<Bits> &val) { return val.template trunc<NewBits>(); } }; template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> zcast() const { return zext_cast<NewBits>()(*this); } template<size_t NewBits> + CXXRTL_ALWAYS_INLINE value<NewBits> scast() const { return sext_cast<NewBits>()(*this); } @@ -439,12 +466,14 @@ struct slice_expr : public expr_base<slice_expr<T, Stop, Start>> { slice_expr(T &expr) : expr(expr) {} slice_expr(const slice_expr<T, Stop, Start> &) = delete; + CXXRTL_ALWAYS_INLINE operator value<bits>() const { return static_cast<const value<T::bits> &>(expr) .template rtrunc<T::bits - Start>() .template trunc<bits>(); } + CXXRTL_ALWAYS_INLINE slice_expr<T, Stop, Start> &operator=(const value<bits> &rhs) { // Generic partial assignment implemented using a read-modify-write operation on the sliced expression. expr = static_cast<const value<T::bits> &>(expr) @@ -453,6 +482,7 @@ struct slice_expr : public expr_base<slice_expr<T, Stop, Start>> { } // A helper that forces the cast to value<>, which allows deduction to work. + CXXRTL_ALWAYS_INLINE value<bits> val() const { return static_cast<const value<bits> &>(*this); } @@ -469,6 +499,7 @@ struct concat_expr : public expr_base<concat_expr<T, U>> { concat_expr(T &ms_expr, U &ls_expr) : ms_expr(ms_expr), ls_expr(ls_expr) {} concat_expr(const concat_expr<T, U> &) = delete; + CXXRTL_ALWAYS_INLINE operator value<bits>() const { value<bits> ms_shifted = static_cast<const value<T::bits> &>(ms_expr) .template rzext<bits>(); @@ -477,6 +508,7 @@ struct concat_expr : public expr_base<concat_expr<T, U>> { return ms_shifted.bit_or(ls_extended); } + CXXRTL_ALWAYS_INLINE concat_expr<T, U> &operator=(const value<bits> &rhs) { ms_expr = rhs.template rtrunc<T::bits>(); ls_expr = rhs.template trunc<U::bits>(); @@ -484,6 +516,7 @@ struct concat_expr : public expr_base<concat_expr<T, U>> { } // A helper that forces the cast to value<>, which allows deduction to work. + CXXRTL_ALWAYS_INLINE value<bits> val() const { return static_cast<const value<bits> &>(*this); } @@ -508,21 +541,25 @@ struct concat_expr : public expr_base<concat_expr<T, U>> { template<class T> struct expr_base { template<size_t Stop, size_t Start = Stop> + CXXRTL_ALWAYS_INLINE slice_expr<const T, Stop, Start> slice() const { return {*static_cast<const T *>(this)}; } template<size_t Stop, size_t Start = Stop> + CXXRTL_ALWAYS_INLINE slice_expr<T, Stop, Start> slice() { return {*static_cast<T *>(this)}; } template<class U> + CXXRTL_ALWAYS_INLINE concat_expr<const T, typename std::remove_reference<const U>::type> concat(const U &other) const { return {*static_cast<const T *>(this), other}; } template<class U> + CXXRTL_ALWAYS_INLINE concat_expr<T, typename std::remove_reference<U>::type> concat(U &&other) { return {*static_cast<T *>(this), other}; } @@ -734,76 +771,119 @@ struct debug_item : ::cxxrtl_object { debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {} template<size_t Bits> - debug_item(value<Bits> &item) { + debug_item(value<Bits> &item, size_t lsb_offset = 0) { static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), "value<Bits> is not compatible with C layout"); - type = VALUE; - width = Bits; - depth = 1; - curr = item.data; - next = item.data; + type = VALUE; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = item.data; + next = item.data; } template<size_t Bits> - debug_item(const value<Bits> &item) { + debug_item(const value<Bits> &item, size_t lsb_offset = 0) { static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), "value<Bits> is not compatible with C layout"); - type = VALUE; - width = Bits; - depth = 1; - curr = const_cast<chunk_t*>(item.data); - next = nullptr; + type = VALUE; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = const_cast<chunk_t*>(item.data); + next = nullptr; } template<size_t Bits> - debug_item(wire<Bits> &item) { + debug_item(wire<Bits> &item, size_t lsb_offset = 0) { static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) && sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t), "wire<Bits> is not compatible with C layout"); - type = WIRE; - width = Bits; - depth = 1; - curr = item.curr.data; - next = item.next.data; + type = WIRE; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = item.curr.data; + next = item.next.data; } template<size_t Width> - debug_item(memory<Width> &item) { + debug_item(memory<Width> &item, size_t zero_offset = 0) { static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t), "memory<Width> is not compatible with C layout"); - type = MEMORY; - width = Width; - depth = item.data.size(); - curr = item.data.empty() ? nullptr : item.data[0].data; - next = nullptr; + type = MEMORY; + width = Width; + lsb_at = 0; + depth = item.data.size(); + zero_at = zero_offset; + curr = item.data.empty() ? nullptr : item.data[0].data; + next = nullptr; } template<size_t Bits> - debug_item(debug_alias, const value<Bits> &item) { + debug_item(debug_alias, const value<Bits> &item, size_t lsb_offset = 0) { static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), "value<Bits> is not compatible with C layout"); - type = ALIAS; - width = Bits; - depth = 1; - curr = const_cast<chunk_t*>(item.data); - next = nullptr; + type = ALIAS; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = const_cast<chunk_t*>(item.data); + next = nullptr; } template<size_t Bits> - debug_item(debug_alias, const wire<Bits> &item) { + debug_item(debug_alias, const wire<Bits> &item, size_t lsb_offset = 0) { static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) && sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t), "wire<Bits> is not compatible with C layout"); - type = ALIAS; - width = Bits; - depth = 1; - curr = const_cast<chunk_t*>(item.curr.data); - next = nullptr; + type = ALIAS; + width = Bits; + lsb_at = lsb_offset; + depth = 1; + zero_at = 0; + curr = const_cast<chunk_t*>(item.curr.data); + next = nullptr; } }; static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout"); -typedef std::map<std::string, debug_item> debug_items; +struct debug_items { + std::map<std::string, std::vector<debug_item>> table; + + void add(const std::string &name, debug_item &&item) { + std::vector<debug_item> &parts = table[name]; + parts.emplace_back(item); + std::sort(parts.begin(), parts.end(), + [](const debug_item &a, const debug_item &b) { + return a.lsb_at < b.lsb_at; + }); + } + + size_t count(const std::string &name) const { + if (table.count(name) == 0) + return 0; + return table.at(name).size(); + } + + const std::vector<debug_item> &parts_at(const std::string &name) const { + return table.at(name); + } + + const debug_item &at(const std::string &name) const { + const std::vector<debug_item> &parts = table.at(name); + assert(parts.size() == 1); + return parts.at(0); + } + + const debug_item &operator [](const std::string &name) const { + return at(name); + } +}; struct module { module() {} @@ -851,271 +931,322 @@ using namespace cxxrtl; // std::max isn't constexpr until C++14 for no particular reason (it's an oversight), so we define our own. template<class T> +CXXRTL_ALWAYS_INLINE constexpr T max(const T &a, const T &b) { return a > b ? a : b; } // Logic operations template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> logic_not(const value<BitsA> &a) { return value<BitsY> { a ? 0u : 1u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> logic_and(const value<BitsA> &a, const value<BitsB> &b) { return value<BitsY> { (bool(a) & bool(b)) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> logic_or(const value<BitsA> &a, const value<BitsB> &b) { return value<BitsY> { (bool(a) | bool(b)) ? 1u : 0u }; } // Reduction operations template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_and(const value<BitsA> &a) { return value<BitsY> { a.bit_not().is_zero() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_or(const value<BitsA> &a) { return value<BitsY> { a ? 1u : 0u }; } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_xor(const value<BitsA> &a) { return value<BitsY> { (a.ctpop() % 2) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_xnor(const value<BitsA> &a) { return value<BitsY> { (a.ctpop() % 2) ? 0u : 1u }; } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> reduce_bool(const value<BitsA> &a) { return value<BitsY> { a ? 1u : 0u }; } // Bitwise operations template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> not_u(const value<BitsA> &a) { return a.template zcast<BitsY>().bit_not(); } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> not_s(const value<BitsA> &a) { return a.template scast<BitsY>().bit_not(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> and_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().bit_and(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> and_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().bit_and(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> or_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().bit_or(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> or_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().bit_or(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> xor_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().bit_xor(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> xor_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().bit_xor(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> xnor_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().bit_xor(b.template zcast<BitsY>()).bit_not(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> xnor_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().bit_xor(b.template scast<BitsY>()).bit_not(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shl_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().template shl(b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shl_su(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().template shl(b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sshl_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().template shl(b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sshl_su(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().template shl(b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shr_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template shr(b).template zcast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shr_su(const value<BitsA> &a, const value<BitsB> &b) { return a.template shr(b).template scast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sshr_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template shr(b).template zcast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sshr_su(const value<BitsA> &a, const value<BitsB> &b) { return a.template sshr(b).template scast<BitsY>(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shift_uu(const value<BitsA> &a, const value<BitsB> &b) { return shr_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shift_su(const value<BitsA> &a, const value<BitsB> &b) { return shr_su<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shift_us(const value<BitsA> &a, const value<BitsB> &b) { return b.is_neg() ? shl_uu<BitsY>(a, b.template sext<BitsB + 1>().neg()) : shr_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shift_ss(const value<BitsA> &a, const value<BitsB> &b) { return b.is_neg() ? shl_su<BitsY>(a, b.template sext<BitsB + 1>().neg()) : shr_su<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shiftx_uu(const value<BitsA> &a, const value<BitsB> &b) { return shift_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shiftx_su(const value<BitsA> &a, const value<BitsB> &b) { return shift_su<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shiftx_us(const value<BitsA> &a, const value<BitsB> &b) { return shift_us<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> shiftx_ss(const value<BitsA> &a, const value<BitsB> &b) { return shift_ss<BitsY>(a, b); } // Comparison operations template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> eq_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY>{ a.template zext<BitsExt>() == b.template zext<BitsExt>() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> eq_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY>{ a.template sext<BitsExt>() == b.template sext<BitsExt>() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> ne_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY>{ a.template zext<BitsExt>() != b.template zext<BitsExt>() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> ne_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY>{ a.template sext<BitsExt>() != b.template sext<BitsExt>() ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> eqx_uu(const value<BitsA> &a, const value<BitsB> &b) { return eq_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> eqx_ss(const value<BitsA> &a, const value<BitsB> &b) { return eq_ss<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> nex_uu(const value<BitsA> &a, const value<BitsB> &b) { return ne_uu<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> nex_ss(const value<BitsA> &a, const value<BitsB> &b) { return ne_ss<BitsY>(a, b); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> gt_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { b.template zext<BitsExt>().ucmp(a.template zext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> gt_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { b.template sext<BitsExt>().scmp(a.template sext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> ge_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { !a.template zext<BitsExt>().ucmp(b.template zext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> ge_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { !a.template sext<BitsExt>().scmp(b.template sext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> lt_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { a.template zext<BitsExt>().ucmp(b.template zext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> lt_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { a.template sext<BitsExt>().scmp(b.template sext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> le_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { !b.template zext<BitsExt>().ucmp(a.template zext<BitsExt>()) ? 1u : 0u }; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> le_ss(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t BitsExt = max(BitsA, BitsB); return value<BitsY> { !b.template sext<BitsExt>().scmp(a.template sext<BitsExt>()) ? 1u : 0u }; @@ -1123,46 +1254,55 @@ value<BitsY> le_ss(const value<BitsA> &a, const value<BitsB> &b) { // Arithmetic operations template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> pos_u(const value<BitsA> &a) { return a.template zcast<BitsY>(); } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> pos_s(const value<BitsA> &a) { return a.template scast<BitsY>(); } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> neg_u(const value<BitsA> &a) { return a.template zcast<BitsY>().neg(); } template<size_t BitsY, size_t BitsA> +CXXRTL_ALWAYS_INLINE value<BitsY> neg_s(const value<BitsA> &a) { return a.template scast<BitsY>().neg(); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> add_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().add(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> add_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().add(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sub_uu(const value<BitsA> &a, const value<BitsB> &b) { return a.template zcast<BitsY>().sub(b.template zcast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> sub_ss(const value<BitsA> &a, const value<BitsB> &b) { return a.template scast<BitsY>().sub(b.template scast<BitsY>()); } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> mul_uu(const value<BitsA> &a, const value<BitsB> &b) { value<BitsY> product; value<BitsY> multiplicand = a.template zcast<BitsY>(); @@ -1180,6 +1320,7 @@ value<BitsY> mul_uu(const value<BitsA> &a, const value<BitsB> &b) { } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> mul_ss(const value<BitsA> &a, const value<BitsB> &b) { value<BitsB + 1> ub = b.template sext<BitsB + 1>(); if (ub.is_neg()) ub = ub.neg(); @@ -1188,6 +1329,7 @@ value<BitsY> mul_ss(const value<BitsA> &a, const value<BitsB> &b) { } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE std::pair<value<BitsY>, value<BitsY>> divmod_uu(const value<BitsA> &a, const value<BitsB> &b) { constexpr size_t Bits = max(BitsY, max(BitsA, BitsB)); value<Bits> quotient; @@ -1209,6 +1351,7 @@ std::pair<value<BitsY>, value<BitsY>> divmod_uu(const value<BitsA> &a, const val } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE std::pair<value<BitsY>, value<BitsY>> divmod_ss(const value<BitsA> &a, const value<BitsB> &b) { value<BitsA + 1> ua = a.template sext<BitsA + 1>(); value<BitsB + 1> ub = b.template sext<BitsB + 1>(); @@ -1222,21 +1365,25 @@ std::pair<value<BitsY>, value<BitsY>> divmod_ss(const value<BitsA> &a, const val } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> div_uu(const value<BitsA> &a, const value<BitsB> &b) { return divmod_uu<BitsY>(a, b).first; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> div_ss(const value<BitsA> &a, const value<BitsB> &b) { return divmod_ss<BitsY>(a, b).first; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> mod_uu(const value<BitsA> &a, const value<BitsB> &b) { return divmod_uu<BitsY>(a, b).second; } template<size_t BitsY, size_t BitsA, size_t BitsB> +CXXRTL_ALWAYS_INLINE value<BitsY> mod_ss(const value<BitsA> &a, const value<BitsB> &b) { return divmod_ss<BitsY>(a, b).second; } diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 2e39891d0..909e1325d 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1627,18 +1627,21 @@ struct CxxrtlWorker { f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = "; dump_const(debug_const_wires[wire]); f << ";\n"; - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(const_" << mangle(wire) << "));\n"; + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(const_" << mangle(wire) << ", "; + f << wire->start_offset << "));\n"; count_const_wires++; } else if (debug_alias_wires.count(wire)) { // Alias of a member wire - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << "));\n"; + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", "; + f << wire->start_offset << "));\n"; count_alias_wires++; } else if (!localized_wires.count(wire)) { // Member wire - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); - f << ", debug_item(" << mangle(wire) << "));\n"; + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(" << mangle(wire) << ", "; + f << wire->start_offset << "));\n"; count_member_wires++; } else { count_skipped_wires++; @@ -1647,8 +1650,9 @@ struct CxxrtlWorker { for (auto &memory_it : module->memories) { if (memory_it.first[0] != '\\') continue; - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(memory_it.second)); - f << ", debug_item(" << mangle(memory_it.second) << "));\n"; + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(memory_it.second)); + f << ", debug_item(" << mangle(memory_it.second) << ", "; + f << memory_it.second->start_offset << "));\n"; } for (auto cell : module->cells()) { if (is_internal_cell(cell->type)) diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc index 489d72da5..e0566e152 100644 --- a/backends/cxxrtl/cxxrtl_capi.cc +++ b/backends/cxxrtl/cxxrtl_capi.cc @@ -47,14 +47,17 @@ size_t cxxrtl_step(cxxrtl_handle handle) { return handle->module->step(); } -cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { - if (handle->objects.count(name) > 0) - return static_cast<cxxrtl_object*>(&handle->objects.at(name)); - return nullptr; +struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts) { + auto it = handle->objects.table.find(name); + if (it == handle->objects.table.end()) + return nullptr; + *parts = it->second.size(); + return static_cast<cxxrtl_object*>(&it->second[0]); } void cxxrtl_enum(cxxrtl_handle handle, void *data, - void (*callback)(void *data, const char *name, cxxrtl_object *object)) { - for (auto &it : handle->objects) - callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second)); + void (*callback)(void *data, const char *name, + cxxrtl_object *object, size_t parts)) { + for (auto &it : handle->objects.table) + callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second[0]), it.second.size()); } diff --git a/backends/cxxrtl/cxxrtl_capi.h b/backends/cxxrtl/cxxrtl_capi.h index 8bd906ea4..599284898 100644 --- a/backends/cxxrtl/cxxrtl_capi.h +++ b/backends/cxxrtl/cxxrtl_capi.h @@ -26,6 +26,7 @@ #include <stddef.h> #include <stdint.h> +#include <assert.h> #ifdef __cplusplus extern "C" { @@ -113,9 +114,15 @@ struct cxxrtl_object { // Width of the object in bits. size_t width; + // Index of the least significant bit. + size_t lsb_at; + // Depth of the object. Only meaningful for memories; for other objects, always 1. size_t depth; + // Index of the first word. Only meaningful for memories; for other objects, always 0; + size_t zero_at; + // Bits stored in the object, as 32-bit chunks, least significant bits first. // // The width is rounded up to a multiple of 32; the padding bits are always set to 0 by @@ -140,17 +147,36 @@ struct cxxrtl_object { // the top-level module instantiates a module `foo`, which in turn contains a wire `bar`, the full // hierarchical name is `\foo \bar`. // -// Returns the object if it was found, NULL otherwise. The returned value is valid until the design -// is destroyed. -struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name); +// The storage of a single abstract object may be split (usually with the `splitnets` pass) into +// many physical parts, all of which correspond to the same hierarchical name. To handle such cases, +// this function returns an array and writes its length to `parts`. The array is sorted by `lsb_at`. +// +// Returns the object parts if it was found, NULL otherwise. The returned parts are valid until +// the design is destroyed. +struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts); + +// Retrieve description of a single part simulated object. +// +// This function is a shortcut for the most common use of `cxxrtl_get_parts`. It asserts that, +// if the object exists, it consists of a single part. If assertions are disabled, it returns NULL +// for multi-part objects. +inline struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { + size_t parts = 0; + struct cxxrtl_object *object = cxxrtl_get_parts(handle, name, &parts); + assert(object == NULL || parts == 1); + if (object == NULL || parts == 1) + return object; + return NULL; +} // Enumerate simulated objects. // // For every object in the simulation, `callback` is called with the provided `data`, the full -// hierarchical name of the object (see `cxxrtl_get` for details), and the object description. +// hierarchical name of the object (see `cxxrtl_get` for details), and the object parts. // The provided `name` and `object` values are valid until the design is destroyed. void cxxrtl_enum(cxxrtl_handle handle, void *data, - void (*callback)(void *data, const char *name, struct cxxrtl_object *object)); + void (*callback)(void *data, const char *name, + struct cxxrtl_object *object, size_t parts)); #ifdef __cplusplus } diff --git a/backends/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/cxxrtl_vcd.h index 4c2021e92..dbeabbaf2 100644 --- a/backends/cxxrtl/cxxrtl_vcd.h +++ b/backends/cxxrtl/cxxrtl_vcd.h @@ -66,11 +66,19 @@ class vcd_writer { } while (ident != 0); } - void emit_var(const variable &var, const std::string &type, const std::string &name) { + void emit_var(const variable &var, const std::string &type, const std::string &name, + size_t lsb_at, bool multipart) { assert(!streaming); buffer += "$var " + type + " " + std::to_string(var.width) + " "; emit_ident(var.ident); - buffer += " " + name + " $end\n"; + buffer += " " + name; + if (multipart || name.back() == ']' || lsb_at != 0) { + if (var.width == 1) + buffer += " [" + std::to_string(lsb_at) + "]"; + else + buffer += " [" + std::to_string(lsb_at + var.width - 1) + ":" + std::to_string(lsb_at) + "]"; + } + buffer += " $end\n"; } void emit_enddefinitions() { @@ -155,7 +163,7 @@ public: emit_timescale(number, unit); } - void add(const std::string &hier_name, const debug_item &item) { + void add(const std::string &hier_name, const debug_item &item, bool multipart = false) { std::vector<std::string> scope = split_hierarchy(hier_name); std::string name = scope.back(); scope.pop_back(); @@ -164,17 +172,20 @@ public: switch (item.type) { // Not the best naming but oh well... case debug_item::VALUE: - emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr), "wire", name); + emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr), + "wire", name, item.lsb_at, multipart); break; case debug_item::WIRE: - emit_var(register_variable(item.width, item.curr), "reg", name); + emit_var(register_variable(item.width, item.curr), + "reg", name, item.lsb_at, multipart); break; case debug_item::MEMORY: { const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); for (size_t index = 0; index < item.depth; index++) { chunk_t *nth_curr = &item.curr[stride * index]; std::string nth_name = name + '[' + std::to_string(index) + ']'; - emit_var(register_variable(item.width, nth_curr), "reg", nth_name); + emit_var(register_variable(item.width, nth_curr), + "reg", nth_name, item.lsb_at, multipart); } break; } @@ -183,7 +194,8 @@ public: // can actually change, and must be tracked. In most cases the VCD identifier will be // unified with the aliased reg, but we should handle the case where only the alias is // added to the VCD writer, too. - emit_var(register_variable(item.width, item.curr), "wire", name); + emit_var(register_variable(item.width, item.curr), + "wire", name, item.lsb_at, multipart); break; } } @@ -192,9 +204,10 @@ public: void add(const debug_items &items, const Filter &filter) { // `debug_items` is a map, so the items are already sorted in an order optimal for emitting // VCD scope sections. - for (auto &it : items) - if (filter(it.first, it.second)) - add(it.first, it.second); + for (auto &it : items.table) + for (auto &part : it.second) + if (filter(it.first, part)) + add(it.first, part, it.second.size() > 1); } void add(const debug_items &items) { |