Select Page

Introduction

We all know the importance of Transaction Level Modelling also known as TLM in UVM. The fundamental goal of TLM is to provide communication between components in a flexible, scalable and yet modular manner. Indeed, TLM does delivers on its promises. Every UVM developer know how to use TLM in their test bench development without knowing the inner workings of it and I think it is absolutely fine to not know how the TLM works, in fact that is the whole point of a methodology or a framework where developers can reuse the code structures without knowing their internal implementation. However, after certain level of experience in using the methodology, gaining insights in to the internals do provide an edge to the developer in deciding best solutions to the problems at hand. In this article, we will peek behind the curtains to get more insights on how UVM’s TLM works. There are several parts to the TLM and a single article cannot do justice to all the parts. So, we focus on analysis communication part of the TLM. We will discuss the remaining parts in upcoming articles.

Setting up the stage

Before diving in to the UVM analysis communication we need to first understand the basics about the communication between two objects. At its simplest form two objects wants to communicate with each other. One of them being the producer of the information and the other being the consumer of the information. To pass on the messages between them, these objects need to know the other party. For example, in what is called a ‘push’ communication model, when the producer  is ready it needs to have a handle of consumer object so it can push the data to consumer. In another communication model called ‘pull’ model, the consumer tries to pull the data from the producer with the help of a handle to producer object. The below diagram show these two models. The solid arrow in figure indicates request initiation and dotted arrow indicates the directions of data.

In both models an object is referencing the other for communication to happen, which means the producer knows about the consumer or consumer knows about the producer. This association to other objects is what we call a tight coupling in object oriented programming and it is considered a bad thing as the objects are not self contained units of reuse and depend on the other objects. Developers should generally aim to reduce the dependencies between the objects (called loose coupling). There is a great deal of detail to be discussed about these basic models (these models are referred as point to point communication models) and they deserve their own articles but for now we will limit our discuss to a specific communication model called a broadcast model.

Special Use Case: Broadcast

We saw very basic models of communication and now let us take a special communication model called a broadcast communication model. In this model, the producer (or broadcaster or publisher) will send messages to multiple recipients (or observers or subscribers). There might be many recipients or no recipients at all. The following diagram shows the broadcast communication model.

A single broadcast component will send out the message and all the subscribers who subscribed to the broadcast will receive the message. There are plenty of examples in real world for this communication model. For example, a whether measuring station can broadcast the measurements to multiple reporting stations. A news channel can broadcast its news feed to multiple subscribers. A podcast can broadcast the audio stream to all its subscribers and so on. This model also allows new subscribers to subscribe or the old ones to unsubscribe without affecting the broadcast.

Interestingly the requirements of analysis layer in UVM are exactly same as broadcast model. The transactor agents sends out the transactions from their internal monitors and these transactions are then analyzed by several components like metric analyzer, coverage collector, predictor, checker etc. The Agent’s monitor is the broadcaster here and all the analysis components are nothing but the subscribers. We will explore how UVM implements broadcasting little later but first we need to understand two popular design patterns that allow us to implement the broadcast communication model. The first one is called an ‘Observer pattern’ and the second one is called ‘Publisher-Subscriber pattern’.

Observer Pattern

The Observer Pattern is a widely used design pattern in software engineering, where an object, known as the ‘Subject’ which contains some information to be shared. It maintains a list of its dependents, called ‘Observers’ and notifies them automatically of any state changes in information it holds, usually by calling one of their methods. It is mainly used to implement distributed event handling systems and is a key part in the well known MVC (Model-View-Controller) architectural pattern. The Subject is the core of the pattern. It keeps the state of the system and, when this state changes, it notifies all its observers. It provides an interface for attaching and detaching Observer objects. Observers are the components that want to be notified about the state changes in the Subject. They provide an update interface which is called by the Subject when its state changes. Note that this pattern can be implemented to support both pull model and push model. In push model the Subject pushes the data along with state change message. In pull model the Subject just informs the observers about the state change and it is the individual Observer who pulls the information from the Subject with another method call. Here is a simple implementation of Observer pattern (push model) in SystemVerilog.

Observer pattern implementation

module observer_pattern;
  
  interface class observer;
    pure virtual function void update(int temperature, int humidity, int pressure);
  endclass : observer

  class report_station implements observer;
    local int temperature;
    local int humidity;
    local int pressure;

    function void update(int temperature, int humidity, int pressure);
      this.temperature = temperature;
      this.humidity = humidity;
      this.pressure = pressure;
      display_weather();   
    endfunction : update

    function void display_weather();
      $display("Current Conditions: Temp = %0d, Humidity = %0d, Pressure = %0d", this.temperature, this.humidity, this.pressure);
    endfunction : display_weather
  endclass : report_station

  class weather_station;
    protected observer observer_list[$];
    protected int temperature;
    protected int humidity;
    protected int pressure;

    function void register_observer(observer i_observer);
      observer_list.push_back(i_observer);
    endfunction : register_observer

    function void remove_observer(observer i_observer);
      foreach (observer_list[i]) begin
        if (observer_list[i] == i_observer) begin
          observer_list.delete(i);
          return;
        end
      end
    endfunction : remove_observer

    protected function void notify_observers();
      foreach (observer_list[i]) begin
        observer_list[i].update(temperature, humidity, pressure);
      end
    endfunction : notify_observers

    function void set_measurements(int temperature, int humidity, int pressure);
      this.temperature = temperature;
      this.humidity = humidity;
      this.pressure = pressure;
      notify_observers();
    endfunction : set_measurements
  endclass : weather_station
    
  initial begin
    weather_station i_weather_station;
    report_station i_report_station;
    i_weather_station = new();
    i_report_station = new();
    
    i_weather_station.register_observer(i_report_station);
    i_weather_station.set_measurements(70, 65, 30);
    i_weather_station.set_measurements(75, 70, 29);
    
    i_weather_station.remove_observer(i_report_station);
    i_weather_station.set_measurements(75, 70, 29);
  end
endmodule : observer_pattern

Notice that the Observer pattern is tightly coupled meaning the Subject knows about the observers and all the observers are directly managed by the Subject for subscription and un-subscriptions. It is already discussed that we don’t want a tight coupling among the objects. The next pattern in our discussion is an extension to the Observer pattern and will solve this issue of tight coupling.

Publisher-Subscriber Pattern

Just like the Observer pattern the Publisher-Subscriber pattern is a design pattern used for achieving the broadcast communication among objects. This pattern is more suited where components are loosely coupled and communicate asynchronously. It is particularly suited for distributed systems and event-driven architectures. In the Publisher-Subscriber pattern, publishers produce data and subscribers consume this data. The publisher doesn’t need to know who the subscribers are, and subscribers don’t need to know about the existence of the publisher. This decoupling is often achieved by using a message broker who sits in the middle and handles all the subscribers. The publisher mediates everything with the help of the broker object like shown in the diagram. A simple implementation should help us understand the details. Here is an implementation example of news channel system in SystemVerilog demonstrating the Publisher-Subscriber pattern.

Publisher Subscriber implementation

module pub_sub_pattern;
  
  interface class subscriber;
    pure virtual function void receive_news(string news);
  endclass : subscriber 
  
  class tv_channel implements subscriber;
    string channel_name;

    function new(string name);
      channel_name = name;
    endfunction : new

    function void receive_news(string news);
      $display("%s received news: %s", channel_name, news);
    endfunction: receive_news
  endclass : tv_channel

  class news_broker;
    subscriber subscribers[$];

    function void add_subscriber(subscriber subscriber);
      subscribers.push_back(subscriber);
    endfunction : add_subscriber

    function void remove_subscriber(subscriber subscriber);
      foreach (subscribers[i]) begin
        if (subscribers[i] === subscriber) begin
          subscribers.delete(i);
          return;
        end
      end
    endfunction : remove_subscriber

    function void distribute_news(string news);
      foreach (subscribers[i]) begin
        subscribers[i].receive_news(news);
      end
    endfunction : distribute_news
  endclass

  class news_publisher;
    protected news_broker broker;

    function new();
      broker = null;
    endfunction : new
    
    function void connect_broker(news_broker broker);
      if(broker) begin
        this.broker = broker;
      end else begin
        $display("Can't take a null object as a broker");
      end
    endfunction : connect_broker

    function void broadcast_news(string news);
      broker.distribute_news(news);
    endfunction : broadcast_news
  endclass : news_publisher


  initial begin
    news_publisher news_station;
    news_broker broker;
    tv_channel tv_channel_1;
    tv_channel tv_channel_2;
    
    news_station = new();
    broker = new();
    news_station.connect_broker(broker);
    tv_channel_1 = new("channel1");
    tv_channel_2 = new("channel2");
    broker.add_subscriber(tv_channel_1);
    broker.add_subscriber(tv_channel_2);

    news_station.broadcast_news("Breaking News: People are learning internals of UVM for better understanding!");
    broker.remove_subscriber(tv_channel_2);
    news_station.broadcast_news("Latest Update: Publisher-Subscriber pattern is trending!");
  end
endmodule : pub_sub_pattern

Notice that this new pattern solves the problem of tight coupling. Also, the news_publisher only deals with news_broker and does not know about the subscribers. Subscribers also subscribe to news_broker and does not know about the news_publisher. This is the loose coupling of objects. Different brokers can be implemented to support different types of services to the subscribers. The Publisher component can be reused without worrying about the details of any subscribers. The Publisher-Subscriber pattern is definitely a one up on observer pattern and best used in cases where we need loose coupling of objects. In UVM, which emphasizes heavily on reuse must have broadcast communication with loose coupling. In the case of UVM the clear winner is publisher-subscriber pattern for implementation of its broadcast communication model.

How UVM does it?

By now, it should be clear that the UVM implements one of the two discussed patterns. Although, the cookbook mentions that it implements Observers pattern, I think it is closer to Publisher-Subscriber pattern than the observer pattern because of loose coupling requirements. Any monitor class can act as a publisher of messages. uvm_analysis_port is a broker class and any analysis component like coverage collector or a checker can act as a subscriber. One difference is that the subscribers in the news channel example need to implement a subscriber interface but the UVM components do not pose any such restrictions. How then, the subscribers are called by the broker (uvm_analysis_port)? Well, there is another object similar to broker on each analysis component called uvm_analysis_imp to provide the call to write() method of the analysis component. Of course, The UVM’s actual implementation is very sophisticated and complex because it needs to support several communication models along with broadcast model under same TLM base infrastructure. Also, It needs to support exports which are intermediate nodes in communication process between ports and implementation. However, underlying implementation follows exactly the same principle regardless of complexity:

    • uvm_monitor (publisher) declares an uvm_analysis_port (broker)
    • coverage_collector (subscriber) declares the uvm_analysis_imp (the broker on the subscriber side)
    • we call monitor.port.connect(coverage_collector.imp) similar to add_subscriber method of news_broker example
    • broadcast is done with port.write() method which is similar to broker.distribute_news() method

Conclusion

We studied several concepts in this article. First, we talked about communication models of objects in general, then we focused on a specific case of broadcasting model. We talked about the design patterns which helps us implementing the broadcast models. We started learning  the Observer pattern and implemented an example in SystemVerilog. We then turned our attention to the second pattern called Publisher-Subscriber pattern, again with an implementation example in SystemVerilog. Finally, we correlated all the learning with UVM and how it uses the same principles to implement its analysis communication model. Not just UVM, the Observer and Pub-Sub pattern that we talked about should help us implement any broadcast messaging requirements that arise in our test bench development. See you in the next one.