Blog root page
previous post in category
next post in category
A common problem in hardware control is that the hardware designers keep coming out with revisions that have basically the same functionality but the bit sheets for register access are completely different than previous versions. This can easily result in methods that look like:
Card::setVoltage (double v)
{
switch (this.model)
{
case "v1.0":
{
// construct write to v1.0 addresses and register fields
}
case "v1.1":
{
// construct write to v1.1 addresses and register fields
}
...
case "v9.6"
{
...
}
}
}
Now every new model requires that one add a clause to the switch statement in every method that accesses the hardware. This gets old very quickly when there are a few thousand semantic values to read or write.
However, one can step back and think about the invariants of writing to hardware are for a single semantic value. The registers are all the same size (usually) and they are subdivided into fields with a start bit and an length. Sometimes values are too large for one register and have to be split across two or three registers. Some values have to be scaled to fit into a fixed number of bits. If a register has multiple fields and one is writing only one of them, one must do a read/modify/write masking operation. However, that's pretty much it.
Given that rather generic view of reading or writing individual values to registers, one can quickly come up with a model something like:
[Value]
| 1
|
| R1
|
| specified by
| 1 1 R2 identifies *
[ValueSpec] ----------------------------- [Register]
| 1 <<ordered>> + address
| + bitCount
| | 1
| R3 |
| | R4
| identifies * * contains |
+----------------- [Field] ------------------+
<<ordered>> + startBit
+ Length
The R3 relationship is ordered to match the R2 relationship. (There is only one field in each register that is relevant to the particular value.) The R4 relationship is fixed for a particular hardware model. The R1 relationship is instantiated dynamically based upon the semantic value to be read or written.
The [Value] object is actually a generic processor for doing the read or write. It "walks" the relationships around [ValueSpec] to construct the actual hardware read or write. It is generic because once R1 is instantiated everything about the read or write is defined.
[Register] and [Field] are specification objects; they simply hold important data values. This model assumes unique instances of [Field] for each Register. When there are a lot of registers some space savings can be achieved by reusing [Field] instances when they are common to different Registers (at the cost of making R3 and R4 *:* relationships). [ValueSpec] is just a place holder for defining the R2 and R3 relationships. (In practice one might implement [ValueSpec] as a dual collection class for the R2 and R3 relationships.)
This is enormously powerful for large device drivers. I worked on a system that had ~10**4 registers to be read or written. The hardware people provided new card models about every six months. We put the bit sheet information into an Excel spreadsheet. That spreadsheet was read by a Factory that also queried the hardware for card models. At startup the Factory initialized all the objects and relationships shown for the hardware actually in place. The interface to this subsystem completely decoupled the rest of the driver (~ 250 KNCLOC) from the bit sheets so that the rest of the driver could deal with the semantics of the values (e.g., logic1VoltageThreshold). Doing this was a major contributing factor in the maintenance team for the driver dropping from eight full-time people to one half-time person.
Blog root page
previous post in category
next post in category
Hola faretaste
mekodinosad
Posted by: AnferTuto | July 28, 2007 at 12:42 PM
Excellent forum, added to favorites!
http://danuegonax.com
Congratulations!
Posted by: kimbscamuck | November 29, 2007 at 03:40 PM