Interfacce

Kratos supports full features of SystemVerilog interface, includes modport. You can create an interface class and use standard OOP techniques available in Python.

Create an interface

First, you need to define an interface class definition. Image we have a buswhere master and slave communicated to each other: he master needs to write/read configuration data from the slave. To define the bus, we have

class ConfigInterface(Interface):
    def __init__(self):
        Interface.__init__(self, "Config")
        width = 8
        # local variables
        read = self.var("read_data", width)
        write = self.var("write_data", width)
        r_en = self.var("read_en", 1)
        w_en = self.var("write_en", 1)
        # common ports
        clk = self.clock("clk")

        # define master -> slave ports
        # and slave -> master ports
        m_to_s= [write, r_en, w_en]
        s_to_m = [read]

        # define master and slave
        self.master = self.modport("Master")
        self.slave = self.modport("Slave")
        for port in m_to_s:
            self.master.set_output(port)
            self.slave.set_input(port)
        for port in s_to_m:
            self.master.set_input(port)
            self.slave.set_output(port)

        # both of them need clock
        self.master.set_input(clk)
        self.slave.set_input(clk)

In the example above, we have Master and Slave connected via a bus using modport. To save our efforts of typing, we group ports together into m_to_s and s_to_m. This also allows better introspection for application-level passes.

To use them, we need some implementation for both master and slave. We will use some dummy logic to illustrate how to use the interface and interact with our logic. To instantiate an interface, you need the following function call from the generator object:

def interface(self, interface, name, is_port: bool = False):

By default the interface bus will be instantiated as local variable, you can set is_port to True to make it a port.s

Here is the code for the master:

class Master(Generator):
    def __init__(self):
        Generator.__init__(self, "Master")

        # instantiate the interface
        self.bus = self.interface(config.master, "bus",
                                  is_port=True)

        # some logic to loop the read and write
        # cycle
        self.counter = self.var("counter", 8)

        # we wire counter value to the write data
        self.wire(self.bus.write_data, self.counter)

        # always_ff on the posedge of clock from the interface
        @always_ff((posedge, self.bus.clk))
        def logic():
            if self.counter % 4 == 0:
                self.bus.r_en = 1
                self.bus.w_en = 0
            elif self.counter % 4 == 1:
                self.bus.r_en = 0
                self.bus.w_en = 1
            else:
                self.bus.r_en = 0
                self.bus.w_en = 0
            self.counter = self.counter + 1

        self.add_always(logic)

Notice that we use a counter to control the read and write enable signal, as well as the write data. Here is the generated SystemVerilog if we call verilog(Master(), filename="master.sv").

module Master (
  Config.Master bus
);

logic [7:0] counter;
assign bus.write_data = counter;

always_ff @(posedge bus.clk) begin
  if ((counter % 8'h4) == 8'h0) begin
    bus.r_en <= 1'h1;
    bus.w_en <= 1'h0;
  end
  else if ((counter % 8'h4) == 8'h1) begin
    bus.r_en <= 1'h0;
    bus.w_en <= 1'h1;
  end
  else begin
    bus.r_en <= 1'h0;
    bus.w_en <= 1'h0;
  end
  counter <= counter + 8'h1;
end
endmodule   // Master

Note

Notice that Kratos will not generate interface if it is used as a modport. This is by design so that we can re-use the interface designed elsewhere such as UVM test files. However, Kratos will generate the interface definition if the entire interface is being used, as we shall see later.

Here is the Python code for the slave:

class Slave(Generator):
    def __init__(self):
        Generator.__init__(self, "Slave")

        # instantiate the interface
        self.bus = self.interface(config.slave, "bus",
                                  is_port=True)

        self.value = self.var("value", 8)

        # just read and write out
        @always_ff((posedge, self.bus.clk))
        def logic():
            if self.bus.r_en:
                self.value = self.bus.write_data
            elif self.bus.w_en:
                self.bus.read_data = self.value

        self.add_always(logic)

Here is generated the SystemVerilog for slave by calling verilog(Slave(), "slave.sv"):

module Slave (
  Config.Slave bus
);

logic [7:0] value;

always_ff @(posedge bus.clk) begin
  if (bus.r_en) begin
    value <= bus.write_data;
  end
  else if (bus.w_en) begin
    bus.read_data <= value;
  end
end
endmodule   // Slave

Now let’s see how we can connect these two generators together:

class Top(Generator):
    def __init__(self):
        Generator.__init__(self, "Top")

        # instantiate master and slave
        self.master = Master()
        self.slave = Slave()
        self.add_child("master", self.master)
        self.add_child("slave", self.slave)

        # clock will be from outside
        clk = self.clock("clk")

        # instantiate the interface bus
        # notice that we're using config, not the modport
        # version such as config.master
        bus = self.interface(config, "bus_top")

        # just need to wire things up
        self.wire(bus.clk, clk)
        self.wire(self.master.bus, bus)
        self.wire(self.slave.bus, bus)
        # the following also works
        # self.wire(self.master.bus, bus.Master)
        # self.wire(self.slave.bus, bus.Slave)

Notice that we instantiate the top level interface bus here as bus = self.interface(config, "bus_top"). Since it’s top-level, it is not a port (default is not). When wiring with the child instances, we can just call self.wire(self.master.bus, bus). Kratos is smart enough to figure out you’re wiring modport interface. If you want consistence semantics as the one from SystemVerilog, you can also do self.wire(self.master.bus, bus.Master), which also works. Since the interface takes clk as an input, we need to wire the signal from the top level, i.e., self.wire(bus.clk, clk). Kratos will figure out the proper port directions.

Here is the generated SystemVerilog. Since we are generating interface at the top level, the interface definition will be generated:

interface Config(
  input logic clk
);
  logic r_en;
  logic [7:0] read_data;
  logic w_en;
  logic [7:0] write_data;
  modport Master(input clk, input read_data, output r_en, output w_en, output write_data);
  modport Slave(input clk, input r_en, input w_en, input write_data, output read_data);
endinterface

// Master and Slave module definition omitted

module Top (
  input logic clk
);

Config bus_top (
  .clk(clk)
);

Master master (
  .bus(bus_top.Master)
);

Slave slave (
  .bus(bus_top.Slave)
);

endmodule   // Top