4.7 KiB
How to Use
When using this library, it's important to note that all the value types behave like regular integers, with the exception that we have a similar bit-width matching check as with Verilog integers. (e.g. a 4-bit register can only be assigned from a 4-bit value)
Additionally, it is recommended to use the provided method for auto-synchronization, which can potentially save you from writing a lot of duplicated code.
Requirements
You will need g++-12
or later, with the flags -std=c++20
.
Example: g++ -std=c++20 ...
Your code may still run on g++-11
or earlier, but we do not guarantee it.
Including the Library
This is a header-only library, which means you simply need to include all the required headers in your project.
We strongly recommend including include/tools.h
to easily include all the headers.
#include "include/tools.h"
Debug Mode
We provide a debug mode, which performs additional checks in the code. To enable this, simply define the macro _DEBUG
before including the headers.
You can also pass -D _DEBUG
to the compiler to define the macro, or define it directly in your code.
#define _DEBUG
We strongly recommend enabling the debug mode when developing your project.
Example: g++ -std=c++20 -D _DEBUG ...
Value Types
Initially, you can treat all these types as Verilog integers. You can assume that all the types below support basic arithmetic operations and will clip the value just like Verilog integer operations.
Register
Registers are similar to those in Verilog.
To simulate registers, a Register
is only allowed to be assigned once in a cycle.
// Declare a 32-bit register
// The maximum bit-width depends on the max_size_t
// Currently, the max_size_t is std::uint32_t
Register<32> reg;
reg <= reg + 1; // OK, allows assignment from a value with the same bit-width
Register<16> reg2;
reg <= reg2 * reg2; // Compile error, the bit-width is different (32 vs 16)
Wire
Wires are also similar to those in Verilog.
They should be assigned exactly once before reading. They can accept a function-like input (function pointers/lambdas) to extract the value.
// Declare a 4-bit wire
Wire<4> wire;
Register<4> reg;
// OK, assign the value from an integer
// Be careful, the value may be clipped
wire = []() { return 0b11010; }; // Clipped to 0b1010
// OK, assign the value from a register
// When the register's value changes,
// the wire's value will also change
Wire <4> wire2 = [®]() -> auto & { return reg; };
// Ill formed! The wire is assigned twice
wire = []() { return 0b11010; };
// Ill formed! Wire cannot accept a value
// with a different bit-width
Wire <5> wire3 = [&]() -> auto & { return reg + 4; };
Bit
Bit is an intermediate type, which can be used to represent an integer with a specific bit width.
Bit <5> b = 0b111111; // Clipped to 0b11111
b.set <4, 2> (0b110); // Set bit 4, 3, 2 to 1, 1, 0
b.set <4> (0); // Set bit 4 to 0
Bit <3> c = b.range <3, 1>; // Copy bit 3, 2, 1 to c
Bit <4> d = b.slice <4> (1); // Copy 4 bits from bit 1 (bit 4, 3, 2, 1) to d
Bit <1> e = d[0]; // Get the 0-th bit of d
Bit f = { b + 3, c, d }; // Concatenate b + 3, c, d from high to low
Synchronization
We support a feature of auto synchronization, which means that you can easily synchronize all the members of a class by simply calling the sync_member
function.
We support 4 types of synchronization:
- Register / Wire type synchronization.
- An array (only std::array is supported) of synchronizable objects.
- A class which contains only synchronizable objects and satisfies std::is_aggregate.
- A class which has some basis that are synchronizable objects, and has a special tag.
We will show some examples of 3 and 4.
Example 1
// An aggregate class, just a pure struct with some member functions.
// No constructor! (That means, do not declare any constructor,
// and the compiler will generate a default constructor for you)
// See https://en.cppreference.com/w/cpp/language/aggregate_initialization
// We support at most 14 members currently.
struct case3 {
Register <3> rs1;
Register <3> rs2;
Register <3> rd;
Wire <3> rs1_data;
Wire <3> rs2_data;
Wire <3> rd_data;
std::array <Register <32>, 32> reg;
};
Example 2
struct some_private {
std::array <Register <16>, 3> private_reg;
};
struct case4 : private some_private, public case3 {
friend class Visitor;
using Tags = SyncTags <case3, some_private>;
};
// The synchronization function
void demo() {
case4 c;
sync_member(c);
}
Common Mistakes
Refer to the mistake page to see some common mistakes.
Examples
See the demo folder for more examples.