This library is being developed in the open. It is considered a work in progress and not ready for production use. It may be buggy, it may not do what it says, it may not even have any source code at this moment.
groov
is an open source hardware register abstraction library. It is designed
to provide a safe, efficient, and maintainable interface to hardware registers
in firmware.
groov
presents an asynchronous interface for accessing registers. Hardware
registers are not always local to the processor's memory bus, they could be
on a remote device over I2C, SPI, CAN, or another interface with a non-trivial
latency. An asynchronous interface is key to robustly and efficiently accessing
registers on remote busses.
groov
uses Intel's baremetal senders and recievers library to
provide a robust asynchronous execution framework.
Hardware registers often contain multiple fields packed in a single word. Each
field may have different access attributes: read/write, read-only, write-only,
write 1 to clear, write 1 to set, etc. When writing a subset of the fields of
a register, it is often necessary to preserve the values of the remaining
fields. groov
calculates whether a read-modify-write is necessary based on
the field attributes, as well as the capabilities of the bus the field is on.
If the bus can access a subset of the register, either through explicit byte-
enables or sub-word access, then groov
will perform this special access if
it means a read-modify-write can be avoided.
// read a register field synchronously
auto op = async::sync_wait(groov::read(mbox / "cmd.opcode"_f));
// read a whole register synchronously
auto cmd = async::sync_wait(groov::read(mbox / "cmd"_r));
// access fields of read result
auto op = cmd["opcode"_f];
auto len = cmd["length"_f];
// write a register
async::sync_wait(
async::just(mbox / "payload"_r[0] = 0xcafed00d) |
groov::write
);
// write multiple fields in a register
async::sync_wait(
async::just(mbox / "cmd"_r(
"start"_f = 1,
"length"_f = 1,
"opcode"_f = opcode_t::MSG
)) |
groov::write
);
async::sync_wait(
groov::read(mbox / "cmd.length"_f) |
async::then([](auto r){
if (r["cmd.length"_f] < 10) {
r["cmd.length"_f]++;
}
return r;
}) |
groov::write
);
Registers are defined declaratively using a simple DSL. Register groups contain registers and registers contain fields. Registers and fields are referenced using compile-time strings.
constexpr auto mbox =
groov::group<"mbox", mmio_bus,
groov::reg<"cmd", 0xfe0'0000, uint32_t,
groov::field<"start", 0, 0, groov::w1s>,
groov::field<"length", 19, 16, groov::rw>,
groov::field<"opcode", 31, 24, groov::rw, opcode_t>
>,
groov::banked_reg<"payload", 0xfe00'0004, uint32_t>
>;
groov
needs to be told how registers in a group can be accessed. The
groov::bus
concept provides this mechanism.
constexpr struct mmio_bus_t {
static async::sender auto read(uint32_t addr);
static async::sender auto write(uint32_t addr, auto mask, uint32_t data);
template<uint32_t mask> static constexpr auto to_bus_mask_v;
template<auto mask> static constexpr uint32_t DataType to_raw_mask_v;
} mmio_bus;
Temporary register values are a fundamental concept of groov
. They are used
for reading, writing, and passing register values around.
If you've been reading this document, then you've already seen them in action.
Below you can see how they are used outside of a read
or write
operation.
// create a temporary of the 'cmd' register.
auto cmd = mbox / "cmd"_r;
cmd["opcode"_f] = opcode_t::CMD;
// writes 'opcode' to CMD, and remaining fields in cmd register to '0'
async::sync_wait(async::just(cmd) | groov::write);
// create a temporary of just the 'cmd.opcode' field.
auto op = mbox / "cmd.opcode"_f;
op = opcode_t::MSG;
// only the opcode field will be updated
// (depending on the bus, a read-modify-write operation may be executed)
async::sync_wait(async::just(op) | groov::write);
groov
mocks out registers by name, not address. This enables groov
to
automatically create mock register groups for you.
groov::mock_group mbox{};
Creating expectations on mocks is straightforward.
GROOV_EXPECT_WRITE(
mbox(
"cmd"_r(
"start"_f = 1,
"opcode"_f = opcode_t::FETCH_PATCH,
"length"_f = 0
)
)
);
GROOV_EXPECT_READ(mbox / "cmd"_r = 0);