diff --git a/Makefile b/Makefile index 8f3e473ea..8a72e3c70 100644 --- a/Makefile +++ b/Makefile @@ -888,6 +888,7 @@ endif +cd tests/verilog && bash run-test.sh +cd tests/xprop && bash run-test.sh $(SEEDOPT) +cd tests/fmt && bash run-test.sh + +cd tests/cxxrtl && bash run-test.sh @echo "" @echo " Passed \"make test\"." @echo "" diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 2d5451287..d861b7e07 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -419,6 +419,7 @@ struct value : public expr_base> { carry = (shift_bits == 0) ? 0 : data[n] >> (chunk::bits - shift_bits); } + result.data[result.chunks - 1] &= result.msb_mask; return result; } @@ -429,12 +430,12 @@ struct value : public expr_base> { // Detect shifts definitely large than Bits early. for (size_t n = 1; n < amount.chunks; n++) if (amount.data[n] != 0) - return {}; + return (Signed && is_neg()) ? value().bit_not() : value(); // Past this point we can use the least significant chunk as the shift size. size_t shift_chunks = amount.data[0] / chunk::bits; size_t shift_bits = amount.data[0] % chunk::bits; if (shift_chunks >= chunks) - return {}; + return (Signed && is_neg()) ? value().bit_not() : value(); value result; chunk::type carry = 0; for (size_t n = 0; n < chunks - shift_chunks; n++) { @@ -443,12 +444,13 @@ struct value : public expr_base> { : data[chunks - 1 - n] << (chunk::bits - shift_bits); } if (Signed && is_neg()) { - size_t top_chunk_idx = (Bits - shift_bits) / chunk::bits; - size_t top_chunk_bits = (Bits - shift_bits) % chunk::bits; + size_t top_chunk_idx = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) / chunk::bits; + size_t top_chunk_bits = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) % chunk::bits; for (size_t n = top_chunk_idx + 1; n < chunks; n++) result.data[n] = chunk::mask; - if (shift_bits != 0) + if (amount.data[0] != 0) result.data[top_chunk_idx] |= chunk::mask << top_chunk_bits; + result.data[result.chunks - 1] &= result.msb_mask; } return result; } @@ -509,7 +511,8 @@ struct value : public expr_base> { for (size_t n = 0; n < chunks; n++) { chunk::type x = data[chunks - 1 - n]; // First add to `count` as if the chunk is zero - count += (n == 0 ? Bits % chunk::bits : chunk::bits); + constexpr size_t msb_chunk_bits = Bits % chunk::bits != 0 ? Bits % chunk::bits : chunk::bits; + count += (n == 0 ? msb_chunk_bits : chunk::bits); // If the chunk isn't zero, correct the `count` value and return if (x != 0) { for (; x != 0; count--) diff --git a/tests/cxxrtl/.gitignore b/tests/cxxrtl/.gitignore new file mode 100644 index 000000000..91caee986 --- /dev/null +++ b/tests/cxxrtl/.gitignore @@ -0,0 +1 @@ +cxxrtl-test-* diff --git a/tests/cxxrtl/run-test.sh b/tests/cxxrtl/run-test.sh new file mode 100755 index 000000000..89de71c6b --- /dev/null +++ b/tests/cxxrtl/run-test.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -ex + +run_subtest () { + local subtest=$1; shift + + ${CC:-gcc} -std=c++11 -O2 -o cxxrtl-test-${subtest} -I../../backends/cxxrtl/runtime test_${subtest}.cc -lstdc++ + ./cxxrtl-test-${subtest} +} + +run_subtest value +run_subtest value_fuzz diff --git a/tests/cxxrtl/test_value.cc b/tests/cxxrtl/test_value.cc new file mode 100644 index 000000000..4d68372bb --- /dev/null +++ b/tests/cxxrtl/test_value.cc @@ -0,0 +1,45 @@ +#include +#include + +#include "cxxrtl/cxxrtl.h" + +int main() +{ + { + // shl exceeding Bits should be masked + cxxrtl::value<6> a(1u); + cxxrtl::value<6> b(8u); + cxxrtl::value<6> c = a.shl(b); + assert(c.get() == 0); + } + + { + // sshr of unreasonably large size should sign extend correctly + cxxrtl::value<64> a(0u, 0x80000000u); + cxxrtl::value<64> b(0u, 1u); + cxxrtl::value<64> c = a.sshr(b); + assert(c.get() == 0xffffffffffffffffu); + } + + { + // sshr of exteeding Bits should sign extend correctly + cxxrtl::value<8> a(0x80u); + cxxrtl::value<8> b(10u); + cxxrtl::value<8> c = a.sshr(b); + assert(c.get() == 0xffu); + } + + { + // Sign extension should occur correctly + cxxrtl::value<64> a(0x23456789u, 0x8abcdef1u); + cxxrtl::value<8> b(32u); + cxxrtl::value<64> c = a.sshr(b); + assert(c.get() == 0xffffffff8abcdef1u); + } + + { + // ctlz should work with Bits that are a multiple of chunk size + cxxrtl::value<32> a(0x00040000u); + assert(a.ctlz() == 13); + } +} diff --git a/tests/cxxrtl/test_value_fuzz.cc b/tests/cxxrtl/test_value_fuzz.cc new file mode 100644 index 000000000..4428e9f6e --- /dev/null +++ b/tests/cxxrtl/test_value_fuzz.cc @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "cxxrtl/cxxrtl.h" + +template +T rand_int(T min = std::numeric_limits::min(), T max = std::numeric_limits::max()) +{ + static_assert(std::is_integral::value, "T must be an integral type."); + static_assert(!std::is_same::value && !std::is_same::value, + "Using char with uniform_int_distribution is undefined behavior."); + + static std::mt19937 generator = [] { + std::random_device rd; + std::mt19937 mt{rd()}; + return mt; + }(); + + std::uniform_int_distribution dist(min, max); + return dist(generator); +} + +struct BinaryOperationBase +{ + void tweak_input(uint64_t &a, uint64_t &b) {} +}; + +template +void test_binary_operation_for_bitsize(Operation &op) +{ + constexpr int iteration_count = 10000000; + + constexpr uint64_t mask = std::numeric_limits::max() >> (64 - Bits); + + using chunk_type = typename cxxrtl::value::chunk::type; + constexpr size_t chunk_bits = cxxrtl::value::chunk::bits; + + for (int iteration = 0; iteration < iteration_count; iteration++) { + uint64_t ia = rand_int() >> (64 - Bits); + uint64_t ib = rand_int() >> (64 - Bits); + op.tweak_input(ia, ib); + + cxxrtl::value va, vb; + for (size_t i = 0; i * chunk_bits < Bits; i++) { + va.data[i] = (chunk_type)(ia >> (i * chunk_bits)); + vb.data[i] = (chunk_type)(ib >> (i * chunk_bits)); + } + + uint64_t iresult = op.reference_impl(Bits, ia, ib) & mask; + cxxrtl::value vresult = op.template testing_impl(va, vb); + + for (size_t i = 0; i * chunk_bits < Bits; i++) { + if ((chunk_type)(iresult >> (i * chunk_bits)) != vresult.data[i]) { + std::printf("Test failure:\n"); + std::printf("Bits: %i\n", Bits); + std::printf("a: %016lx\n", ia); + std::printf("b: %016lx\n", ib); + std::printf("iresult: %016lx\n", iresult); + std::printf("vresult: %016lx\n", vresult.template get()); + + std::terminate(); + } + } + } + std::printf("Test passed @ Bits = %i.\n", Bits); +} + +template +void test_binary_operation(Operation &op) +{ + // Test at a variety of bitwidths + test_binary_operation_for_bitsize<8>(op); + test_binary_operation_for_bitsize<32>(op); + test_binary_operation_for_bitsize<42>(op); + test_binary_operation_for_bitsize<63>(op); + test_binary_operation_for_bitsize<64>(op); +} + +template +struct UnaryOperationWrapper : BinaryOperationBase +{ + Operation &op; + + UnaryOperationWrapper(Operation &op) : op(op) {} + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return op.reference_impl(bits, a); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return op.template testing_impl(a); + } +}; + +template +void test_unary_operation(Operation &op) +{ + UnaryOperationWrapper wrapped(op); + test_binary_operation(wrapped); +} + +struct ShlTest : BinaryOperationBase +{ + ShlTest() + { + std::printf("Randomized tests for value::shl:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return b >= 64 ? 0 : a << b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.shl(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} shl; + +struct ShrTest : BinaryOperationBase +{ + ShrTest() + { + std::printf("Randomized tests for value::shr:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return b >= 64 ? 0 : a >> b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.shr(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} shr; + +struct SshrTest : BinaryOperationBase +{ + SshrTest() + { + std::printf("Randomized tests for value::sshr:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + int64_t sa = (int64_t)(a << (64 - bits)); + return sa >> (b >= bits ? 63 : (b + 64 - bits)); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.sshr(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} sshr; + +struct AddTest : BinaryOperationBase +{ + AddTest() + { + std::printf("Randomized tests for value::add:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return a + b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.add(b); + } +} add; + +struct SubTest : BinaryOperationBase +{ + SubTest() + { + std::printf("Randomized tests for value::sub:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return a - b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.sub(b); + } +} sub; + +struct CtlzTest +{ + CtlzTest() + { + std::printf("Randomized tests for value::ctlz:\n"); + test_unary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a) + { + if (a == 0) + return bits; + return __builtin_clzl(a) - (64 - bits); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a) + { + size_t result = a.ctlz(); + return cxxrtl::value((cxxrtl::chunk_t)result); + } +} ctlz; + +int main() +{ +}