hdl.cpp is an RTL intermediate representation for C++. hdl.cpp targets code generation and analysis tasks. It makes extensive use of hashconsing and on the fly simplifications. Besides the core IR, the library provides the following features:
- Simulation
- Visualization
- Verilog Code Generation
- Verilog Frontend via a Yosys Plugin
- Code Generation for Yosys' RTLIL
- DSL for hardware description
- Theorem Proving with Z3 or bit-blasting
- Serialization using a text based format
Note: hdl.cpp is currently highly unstable.
hdl::Module
is the core class of hdl.cpp's IR.
It represents a hardware description in register-transfer level.
Here is an example of a 4 bit counter:
hdl::Module module("top");
hdl::Value* clock = module.input("clock", 1);
hdl::Reg* counter = module.reg(hdl::BitString("0000"), clock);
counter->next = module.op(hdl::Op::Kind::Add, {
counter,
module.constant(hdl::BitString("0001"))
});
module.output("counter", counter);
The Constant* Module::constant(const BitString& bit_string)
method is used to create a Constant from a BitString.
Constants are deduplicated using hashconsing.
Arbitrary bit values are represented by the hdl::BitString
class.
The following constructors are available:
BitString(const std::string& string)
wherestring
is a binary numberstatic BitString from_bool(bool value)
where the resulting BitString is of width 1template <class T> static BitString from_uint(T value)
where the resulting BitString is of widthsizeof(T) * 8
BitString from_base_log2(size_t base_log2, const std::string& string)
wherestring
is a number in base2 ** base_log2
BitString from_oct(const std::string& string)
wherestring
is an octal numberBitString from_hex(const std::string& string)
wherestring
is a hexadecimal number
BitStrings do not record the signedness of their value. Instead signedness is part of the operators applied on the BitString.
Operators are created using the Value* Module::op(Op::Kind kind, const std::vector<Value*>& args)
method.
Instantiated operators are deduplicated using hashconsing.
On the fly simplifications will also be applied.
If all arguments of an operator are constants, constant propagation is always applied.
Additionally simple local simplifications are applied.
E.g. module.op(hdl::Op::Kind::Not, {module.op(hdl::Op::Kind::Not, {a})}) == a
is true.
The following operators are available for representing combinatorial logic.
Operator | Type |
---|---|
And | (a, a) -> a |
Or | (a, a) -> a |
Xor | (a, a) -> a |
Not | a -> a |
Add | (a, a) -> a |
Sub | (a, a) -> a |
Mul | (a, b) -> a + b |
Eq | (a, a) -> 1 |
LtU | (a, a) -> 1 |
LtS | (a, a) -> 1 |
Concat | (a, b) -> a + b |
Slice | (a, Constant[o], Constant[w]) -> w |
Shl | (a, b) -> a |
ShrU | (a, b) -> a |
ShrS | (a, b) -> a |
Select | (1, a, a) -> a |
Memories can be created using the Memory* Module::memory(size_t width, size_t size)
method.
Modules can be simulated using hdl::sim::Simulation
.
The hdl::sim::Simulation::update
method performs a simulation step with the given inputs.
Outputs can be read back using hdl::sim::Simulation::outputs
or hdl::sim::Simulation::find_output
.
bool clock = false;
for (size_t step = 0; step < 32; step++) {
simulation.update({hdl::BitString::from_bool(clock)});
std::cout << simulation.outputs()[0] << std::endl;
clock = !clock;
}
The IR can be visualized using GraphViz.
hdl::graphviz::Printer gv_printer(module);
gv_printer.save("graph.gv");
hdl::verilog::Printer
is used to generate Verilog source code for a given module.
hdl::verilog::Printer printer(module);
printer.print(std::cout);
hdl::textir::Printer
is used to serialize modules.
hdl::textir::Printer textir_printer(module);
textir_printer.save("top.textir");
Here is the textir
file for the counter module from above:
0 = input "clock" 1
1 = reg 4'b0 ""
2 = constant 4'b1
3 = Add 1 2
next 1 0 3
output "counter" 1
You can use a hdl::textir::Reader
to deserialize a module from disk or a string.
static Module textir::Reader::read_module(std::istream& stream)
static Module textir::Reader::load_module(const char* path)
hdl.cpp supports theorem proving using Z3 and using generic SAT solvers using bit-blasting.
Check out the hdl_proof_z3.cpp
and hdl_proof.cpp
examples respectively.
When writing test cases for analysis passes, it may be cumbersome to use the hdl::Module
API.
This is why hdl.cpp provides a domain specific language for high level hardware description inside C++.
Check out the hdl_dsl.cpp
example.
hdl.cpp provides a yosys plugin which can be used as a verilog frontend.
It can convert the RTLIL intermediate language to a textir
file.
yosys -m hdl.so -p "read_verilog top.v; hierarchy; proc; flatten; opt_expr; opt_clean; write_hdl -top top top.textir"
Copyright 2023 Can Joshua Lehmann
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.