docs: add more document
This commit is contained in:
156
README.md
156
README.md
@ -4,159 +4,29 @@ A template which enables you to write verilog-like C++ code.
|
|||||||
|
|
||||||
## How to use
|
## 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.
|
In addition, we provide a debug macro control to perform more runtime check
|
||||||
|
to help user to debug the code.
|
||||||
```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 <Register <32>, 32> reg;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Example 2
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deficiencies
|
## 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 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.
|
- We do no support `signed` types now.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
164
docs/help.md
Normal file
164
docs/help.md
Normal file
@ -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 <Register <32>, 32> reg;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
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
|
||||||
|
|
||||||
|
Turn to the [mistake](mistake.md) page to see some common mistakes.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
See demo folder for more examples.
|
52
docs/mistake.md
Normal file
52
docs/mistake.md
Normal file
@ -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 <Register <1>, 8> data;
|
||||||
|
std::array <Wire <1>, 8> data2;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Some others
|
||||||
|
|
||||||
|
If you encounter some other issues, please feel free to open an issue.
|
Reference in New Issue
Block a user