In the last two articles (here and here,) I discussed the dangers of read-modify-write operations on registers that create exposure to collision and corruptions by firmware. I discussed certain firmware and CPU techniques that can reduce these dangers, but not completely eliminate them. As promised, this month I will discuss a register design that eliminates those dangers and provides atomic register access. Actually, I have three designs to discuss.
This register diagram shows a typical read/write register with non-atomic access:
MSB | Non-Atomic Register (0x0020) | LSB | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Pos | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Bit | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | H | G | F | E | D | C | B | A |
R/W | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
To set bit C and clear bit F, firmware reads the register, 0x0000 00A1, modifies the content, then writes 0x0000 0085 to make sure bits A and H stay set and the other bits stay cleared.
Instead of one register address with write access (W) to all bits, the first atomic register design (Atomic Register 1) uses two register addresses, one for setting bits (Write-1-Set, W1S) and one for clearing bits (Write-1-Clear, W1C). These two address locations affect the same internal register. To set a bit, write a 1 to the corresponding location in the W1S register. To clear a bit, write a 1 to the corresponding bit in the W1C register. This diagram illustrates these two registers:
MSB | Atomic Register 1 (W1S 0x0020, W1C 0x0024)) | LSB | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Pos | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Bit | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | H | G | F | E | D | C | B | A |
R/W1S | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
R/W1C | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
Reading from either the W1S or W1C register will return the current contents of the internal register. However, reading is not a pre-requisite to writing as in the case with the non-atomic register. Writing can be done without first knowing the current contents of the register. This reduces the read-modify-write operation to simply one or two writes.
To set bit C and clear bit F, firmware would write 0x0000 0004 to the W1S register and 0x0000 0020 to the W1C register. Reading either register address after both writes (assuming bits A and H are already set) would return 0x0000 0085.
An example of this architecture can be found in the PCI Express to Serial ATA Controller by Silicon Image for their Port Interrupt Enable Set/Clear register. (See page 66 in here.)
A second design (Atomic Register 2) is used in the CAN Serial Communications Controller by Intel (see pages 7-23 here.) Two adjacent bits operate as a pair for reading and writing a single value. Reading 01 from a bit pair represents a value of 0 and 10 represents a value of 1. Writing 01 or 10 to a bit pair clears or sets the associated value, respectively. Writing 11 to a bit pair leaves the corresponding value unchanged. (Writing 00 in this implementation is not allowed and is indeterminate, leaving it exposed to potential undesired behavior by incorrect firmware. That exposure can be eliminated by designing it so that 00, like 11, results in no change.) Here is a register diagram using this design:
MSB | Atomic Register 2 (0x0020) | LSB | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Pos | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Bit | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | – | H | H | G | G | F | F | E | E | D | D | C | C | B | B | A | A |
R/W2 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 |
Using the same example, setting position C and clearing position F (remember, there are two bits for every position) is accomplished by writing 0xFFFF F7EF. Again assuming that positions A and H are already set and the rest cleared, reading this register would return 0x5555 9566. This method has the advantage in that with one register write, bits can be set, cleared, and left unchanged as desired. However, the method is awkward to use when writing and debugging code, and for a human to decode the hex values.
A third and better solution (Atomic Register 3) is to allocate the upper half of the register to control which bits to modify and the lower half to specify how to modify the bits. A 1 written to a bit position in the upper half will allow the corresponding bit in the lower half to be written a 1 or a 0. A 0 written to a bit in the upper half will leave the corresponding bit in the lower half unchanged.
MSB | Atomic Register 3 (0x0020) | LSB | ||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Pos | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Bit | – | – | – | – | – | – | – | – | h | g | f | e | d | c | b | a | – | – | – | – | – | – | – | – | H | G | F | E | D | C | B | A |
R/W2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
Setting bit C and clearing bit F would entail writing the value, 0x0024 0004, to the register. The upper half of the value, 0x0024, specifies which bits to modify, bits C and F. The lower half, 0x0004, says to set bit C and clear bit F. Now, reading the register (again, with bits A and H already set) returns 0x0000 0085. That is much easier for a human to read than 0x5555 9566.
All three of these designs provide atomic register access; they completely eliminate the read-modify-write dangers. The first design requires two address locations and two writes for one register but can handle the full register (32 bits in the above examples). The second and third method requires only one address and one write but can only handle half the register size (16 bits in the above examples.) The first and third designs are better than the second, which—for reasons stated above—should be avoided.
Until the next W1S newsletter…