3

I'm working on a program that tests a digital circuit. The digital circuit can process accesses from multiple different caches, CACHE0, CACHE1, etc. The digital circuit can handle accesses to different memories MEM0, MEM1, etc. and a special kind of memory SPECIAL_MEM. The special memory can be accessed in multiple modes.

Here's a block diagram of my setup: enter image description here

The design under test (DUT) is the digital circuit I am testing.

The monitoring block looks at the input and output signals of the DUT and figures out what kind of access is going on (e.g. from CACHE0 to MEM1). It creates one object for the current operation and sends it to the testbench.

The testbench performs different tasks on the objects it receives:

  • logging of operations that are seen to allow for easy debug
  • checks that certain requirements are met w.r.t. the operations (e.g. that the data written from the cache is the same as the one written to the memory)
  • covers which operations were seen during the test run and stores this information in a special kind of database (e.g. we've seen that we've done accesses from CACHE1 to all memories except to SPECIAL_MEM)

I need to be able to model all types of accesses that can happen. If I think in term of properties that an access has, these are the cache_kind and the memory_kind. When the memory_kind is SPECIAL_MEM, though, there is also the special_mem_mode property. In my real life project there are actually many more properties I need to handle, but I want to keep the example brief and (hopefully) understandable.

Another thing to keep in mind is that in my professional field there is an over-reliance on data objects, as opposed to using objects with polymorphic behavior. You observe the inputs of the digital circuit, waiting for changes that signal a new access, create an object that describes that access and send it to different parts of your program to do different things based on it.

I'm working in an object oriented programming language called SystemVerilog, which borrows a bit from C++ and Java. I'm not sure what the best way is to model my accesses.

I could create a base interface for an access that contains the properties that are common to all accesses:

interface class access;

  pure virtual function cache_kind_e get_cache_kind();
  pure virtual function mem_kind_e get_mem_kind();

endclass

I could then create a specialized interface for the access to the special memory:

interface class special_mem_access extends access;

  pure virtual function special_mem_mode_e get_mode();

endinterface

The special_mem_access interface contains the get_mem_kind() function, though, which should always return SPECIAL_MEM for a special_mem_access object. This feels off, because there is no way to enforce this.

There is a programming language in my field called e, where it's possible to do something called when subtyping. I used to program in this language, but had to switch to SystemVerilog. In e we would model the accesses like this:

struct access {

  cache_kind: cache_kind_e;
  mem_kind: mem_kind_e;

  when SPECIAL_MEM'mem_kind {
    mode: special_mem_mode_e;
  };

};

In this case, there is only one access class, but the compiler knows that only objects whose mem_kind is SPECIAL_MEM also have the mode property. In order to access it, the compiler has to be certain that this is the case, by forcing the user to do a cast, so it's impossible to access the mode field for an object whose mem_kind isn't SPECIAL_MEM.

This mechanism of when inheritance, which relies on properties, is what probably kicked off the over-reliance on data objects. After migrating to SystemVerilog, the habits stayed the same.

Coming back to the traditional OOP world, would it be better to do away with the get_mem_kind() function entirely and encode this in the type itself? Concretely, we would have one interface for each mem_kind:

interface class access;

  pure virtual function cache_kind_e get_cache_kind();

endinterface


interface class mem0_access extends access;
endinterface

interface class mem1_access extends access;
endinterface

// ...


interface class special_mem_access extends access;

  pure virtual function special_mem_mode_e get_mode();

endinterface

This way we do away with the awkwardness of having a function in special_mem_access that trivially returns SPECIAL_MEM. At the same time, getting the mem_kind by querying the type of the object is most probably a code smell. We also open up the possibility for a class to implement access directly, instead of one of its sub-types, leading to a new inconsistency.

I can also imagine moving the get_mem_kind() function to an interface parallel to special_mem_access:

interface class non_special_mem_access extends access;

  pure virtual function mem_kind_e get_mem_kind();

endclass

Finally, one could simply put all methods inside a single interface and document that the get_mode() function is only meant to be used for objects whose mem_kind is SPECIAL_MEM:

interface class access;

  pure virtual function cache_kind_e get_cache_kind();
  pure virtual function mem_kind_e get_mem_kind();
  pure virtual function special_mem_mode_e get_mode();

endclass

If the value of get_mode() is not defined, the object could issue a fatal error if the function is called.

I've tried to find resource online that deal with this topic, but I haven't had much luck. I'm curious how to best model this heterogeneity in properties an object has in an OOP language. Moreover, there may be many different dimensions that cause such heterogeneity: for example, there could be another MORE_SPECIAL_MEM that has other ways it can be accessed, there could be different access modes for SPECIAL_CACHE, etc.

2
  • 2
    Your question shows research effort, yet I'm reading it and I can't understand what you want to achieve exactly with this interfaces in the first place. It seems to me that you create interfaces for objects that don't have any relation, just to ask them what their type is. Can you please clarify it in your question ?
    – nadir
    Jul 19, 2019 at 12:25
  • @nadir I've updated the introduction with a bit more information about what I'm doing. I hope this makes my question more understandable. Please let me know if I need to improve it further.
    – Tudor Timi
    Aug 8, 2019 at 10:51

1 Answer 1

2

Interfaces should be used as an API to different users of the class.
You should strive to create an interface that provides a uniform API for the users of objects implementing such interface.
The user should interact with an object implementing interface "A" without caring about the actual runtime type of the object (ideally) or how exactly it implements it.


In how you presented your question, you try to create interface for objects with the sole intention of receiving their data members.
Getting object's data members stands in stark contrast to what I described above - as you are making an assumption regarding the data members of objects implementing the interface.
As you request data members and fields of an object, and not so much care about executing some functionalities on them in a uniform matter - you ask for a specialized API and (essentially) you ask for the specific object type itself (in how you described it).


You should rethink your design. What exactly you try to achieve here, when you create interface to this objects ? (as this is not the use-case of interfaces in OOP)
If you are trying to reuse specific members/implementation details between objects, you can use aggregation/private inheritance and expose what you need.
If you can use the specific object type in your instances of usage, your problem is solved.
The other solutions as you mentioned here ("when" functionality) are to write fat interfaces that divulge a lot of specialized information from the object, or to execute functionality based on RTTI / your implementation to it. Using such things can indicate a bad design (as I noted for you here).

3
  • I get your points. I guess the main issue stems from the fact that such a design doesn't follow the 'tell, don't ask' principle.
    – Tudor Timi
    Aug 29, 2019 at 15:35
  • I guess something like the visitor pattern would also be of help here. This seems to fit very well with the example on Wikipedia (en.wikipedia.org/wiki/Visitor_pattern). The objects there, Literal and Addition have different data members, but one still would want to "process" them in a generic way.
    – Tudor Timi
    Aug 29, 2019 at 15:38
  • If you just need to visit and process your entities independently, that is true. Yet, in how you phrased your question it doesn't seem that each entity is independent in its processing (otherwise, why you need to get their specialized data member and invoke specialized routine based on 'when').
    – nadir
    Aug 31, 2019 at 18:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.