From d89885cd97db5cc1806e8a58b5e3b0153970560a Mon Sep 17 00:00:00 2001 From: DarkSharpness <2040703891@qq.com> Date: Fri, 12 Jul 2024 11:02:42 +0800 Subject: [PATCH] docs: add more document --- README.md | 156 ++++----------------------------------------- docs/help.md | 164 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/mistake.md | 52 +++++++++++++++ 3 files changed, 229 insertions(+), 143 deletions(-) create mode 100644 docs/help.md create mode 100644 docs/mistake.md diff --git a/README.md b/README.md index 98eb2da..51070a0 100644 --- a/README.md +++ b/README.md @@ -4,159 +4,29 @@ A template which enables you to write verilog-like C++ code. ## How to use -### Requirements +Go to [docs/help.md](docs/help.md) for more information. -`g++-12` or later. `-std=c++20` or `-std=c++2b`. +## Design -e.g. `g++ -std=c++2b ...` +We propose this template to better simulate the behavior of real hardware. +That is, register's value will be updated in the next cycle after assigned, +and wire's value will be updated with respect to the connected register or wire. -### Include the library +However, it's not easy to synchronize the values of registers and wires in C++ simulation, +since user may forget to synchronize the value after a cycle is done. -This is a header-only library, which means you just need to include all your required headers in your project. +Therefore, we purpose such a framework, which features automatic value +synchronization, to help user to write the code in a safer way. +Since the code is written in C++, IDE will have better code completion +and highlight support (than Verilog), and user can debug the code with ease. -We strongly recommend you to include `include/tools` to simply include all the headers. - -```cpp -#include "include/tools.h" -``` - -### Debug mode - -We provide a debug mode, which will perform more checks in the code. To enable that, -just define the macro `_DEBUG` before including the headers. -You may also pass `-D _DEBUG` to the compiling command to define the macro. - -```cpp -#define _DEBUG -``` - -### Components - -You may at first treat all these components as the verilog integers. -You may assume all the types below support basic arithmetic operations, -and will clip the value just as the verilog integers operations. - -#### Register - -Registers are just like the registers in the verilog. - -To simulate the registers, a `Register` is only allowed to be assigned once in a cycle. - -```cpp -// 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, allow to assign from some 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 the wires in the verilog. - -It should be assigned exactly once before reading. -It can accept a function-like input (function pointers/lambdas) to extract the value. - -```cpp -// 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 can not accept a value -// with 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 some bit_width. - -```cpp -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: - -1. Register / Wire type synchronization. -2. An array (only std::array is supported) of synchronizable objects. -3. A class which contains only synchronizable objects, and satisfies std::is_aggregate. -4. A class which has some basis which are synchronizable objects, and has a special tag. - -We will show some examples of 3 and 4. - -#### Example 1 - -```cpp -// 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 , 32> reg; -}; -``` - -#### Example 2 - -```cpp -struct some_private { - std::array , 3> private_reg; -}; - -struct case4 : private some_private, public case3 { - friend class Visitor; - using Tags = SyncTags ; -}; - -// The synchronization function -void demo() { - case4 c; - sync_member(c); -} -``` +In addition, we provide a debug macro control to perform more runtime check +to help user to debug the code. ## Deficiencies - We do not support Combination Circuit directly now. You may simulate that by simpling using normal integers as intermediate values, and arrange a good order to update the values. -- We have not implement all the operators on those `integer-like` types. This will be done in the near future. - - We do no support `signed` types now. ## TODO diff --git a/docs/help.md b/docs/help.md new file mode 100644 index 0000000..0e66470 --- /dev/null +++ b/docs/help.md @@ -0,0 +1,164 @@ +# How to use + +You need to keep in mind that all the value types just +behave like normal integers, except that we have a similar +bit-width matching check as the verilog integers. +(e.g. 4-bit register can only be assigned from a 4-bit value) + +Also, you should use the recommended way to perform the auto-synchronization, +which can (hope so) save you from writing a lot of duplicated code. + +## Requirements + +`g++-12` or later. `-std=c++20` or `-std=c++2b`. + +e.g. `g++ -std=c++2b ...` + +## Include the library + +This is a header-only library, which means you just need to include all your required headers in your project. + +We strongly recommend you to include `include/tools` to simply include all the headers. + +```cpp +#include "include/tools.h" +``` + +## Debug mode + +We provide a debug mode, which will perform more checks in the code. To enable that, +just define the macro `_DEBUG` before including the headers. +You may also pass `-D _DEBUG` to the compiling command to define the macro. + +```cpp +#define _DEBUG +``` + +## Value types + +You may at first treat all these types as the verilog integers. +You may assume all the types below support basic arithmetic operations, +and will **clip** the value just as the verilog integers operations. + +### Register + +Registers are just like the registers in the verilog. + +To simulate the registers, a `Register` is only allowed to be assigned once in a cycle. + +```cpp +// 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, allow to assign from some 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 the wires in the verilog. + +It should be assigned exactly once before reading. +It can accept a function-like input (function pointers/lambdas) to extract the value. + +```cpp +// 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 can not accept a value +// with 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 some bit_width. + +```cpp +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: + +1. Register / Wire type synchronization. +2. An array (only std::array is supported) of synchronizable objects. +3. A class which contains only synchronizable objects, and satisfies std::is_aggregate. +4. A class which has some basis which are synchronizable objects, and has a special tag. + +We will show some examples of 3 and 4. + +### Example 1 + +```cpp +// 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 , 32> reg; +}; +``` + +### Example 2 + +```cpp +struct some_private { + std::array , 3> private_reg; +}; + +struct case4 : private some_private, public case3 { + friend class Visitor; + using Tags = SyncTags ; +}; + +// The synchronization function +void demo() { + case4 c; + sync_member(c); +} +``` + +## Common mistakes + +Turn to the [mistake](mistake.md) page to see some common mistakes. + +## Examples + +See demo folder for more examples. diff --git a/docs/mistake.md b/docs/mistake.md new file mode 100644 index 0000000..151e2de --- /dev/null +++ b/docs/mistake.md @@ -0,0 +1,52 @@ +# Some common mistakes + +## Bit-width mismatch + +That is, the bit-width of the LHS and RHS of an assignment operation are different. +For example, the following code will result in compile error: + +```cpp +Register <8> r1; +Register <16> r2; +r1 <= r2; // Error: bit-width mismatch +``` + +## Register/Wire passed by value + +Register/Wire can be only passed by reference. We forbid +the copy/move constructor for Register/Wire to avoid misuse. + +This may cause some error in the lambda function of a wire. + +```cpp +Register <8> r1; +Wire <8> w1 = [&]() { return r1; }; +``` + +To fix this issue, you may return by reference, +or use + operator to convert the value to bit type. + +```cpp +Register <8> r1; +Wire <8> w1 = [&]() -> auto & { return r1; }; +Wire <8> w2 = [&]() { return +r1; }; +``` + +## C-array as member variable + +We do not support C-array as member variable for synchronization. +Our C++ static reflection library do not support parsing +C-array as member variable currently. + +Always use `std::array` instead. + +```cpp +struct NeedToSync { + std::array , 8> data; + std::array , 8> data2; +}; +``` + +## Some others + +If you encounter some other issues, please feel free to open an issue.