When a miss occurs, that is, when a read request is received for a word and the word is not present in the cache, we have to bring the block to cache.
There are two possibilities in case of a miss:
If the set is not full, the counter associated with the new block loaded from the main memory is set to 0, and the values of all other counters are incremented by 1.
If the set is full and a miss occurs, the block with the counter value 3 is removed , and the new block is put in its palce. The counter value is set to zero. The other three block counters are incremented by 1.
It is easy to verify that the counter values of occupied blocks are always distinct. Also it is trivial that highest counter value indicates least recently used block.