{josuah.net} | {panoramix-labs.fr}
 (DIR)  • {josuah.net}
 (DIR)  • {panoramix-labs.fr}
       
        {git} | {cv} | {links} | {quotes} | {ascii} | {tgtimes} | {gopher} | {mail}
 (DIR)  • {git}
 (BIN)  • {cv}
 (DIR)  • {links}
 (DIR)  • {quotes}
 (DIR)  • {ascii}
 (HTM)  • {tgtimes}
 (DIR)  • {gopher}
       
       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
       Interface in Open-Source SystemVerilog Synthesis
       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
        Verilog lacks a way to build complex data types, akin to C's `struct`.
       
 (HTM)  SystemVerilog introduces a {`struct`} feature, but  it cannot declare `input`s
        and `output`s.
       
 (HTM)  SystemVerilog introduces an {`interface`} feature for use in module ports,
 (HTM)  along with {`modport`}.
       
        It simplify connections between modules, that do not need to declare the
        signals one by one anymore.
       
        For instance for bundling together the many signals of
       
        • a peripheral on a bus (like Wishbone, AXI, PCIe...),
       
        • an external protocol (like SPI, UART, I²C, DDR memories, ...),
       
        • interrupts lines,
       
        • DMA ports,
       
        • clock domain crossing,
       
       Declaring an interface
       ──────────────────────
        The syntax follows the same as the `module` keyword:
       
        ┊ interface bus_if;
        ┊   logic req;
        ┊   logic ack;
        ┊   logic[7:0] data;
        ┊ endinterface
       
        There is an implicit typedef in interface declaration: `bus_if` then becomes a
        type that you can use instead of `logic`, `wire`, `reg`, etc.
       
       Using the interface
       ───────────────────
        The type can be used anywhere You can also access the interface individual
        signals with the dot notation as below.
       
        ┊ module lighter (
        ┊   input logic clk,
        ┊   bus_if bus,  // the interface is passed like any signal
        ┊   output logic[2:0] rgb_leds
        ┊ );
        ┊   assign bus.ack = bus.req;
        ┊ 
        ┊   always_ff @(posedge clk) begin
        ┊     if (bus.req) begin
        ┊       rgb_leds <= bus.data[2:0];
        ┊     end
        ┊   end
        ┊ endmodule
       
        Instanciating a verilog module goes by calling its name and filling all
        signals.
       
        Interfaces are passed in the list of signals as if it was a regular signal.
       
        For that, the interface must be instantiated separately:
       
        ┊ module top (
        ┊   input logic clk
        ┊ );
        ┊   bus_if bus ();
        ┊   // at this point, "bus" is an interface with empty signals,
        ┊   // each signal has to be integrated in the parent module as
        ┊   // needed
        ┊ 
        ┊   mBusController controller (
        ┊     .clk(clk),
        ┊     .bus(bus)  // writes to "req" and "data", reads from "ack"
        ┊   );
        ┊ 
        ┊   lighter peripheral1 (
        ┊     .clk(clk),
        ┊     .bus(bus)  // reads from "req" and "data", writes to "ack"
        ┊   );
        ┊ endmodule
       
        Note that input or output directions are not described, which is the role of
        modport.
       
       Declaring modports
       ──────────────────
        Modport specify the input or output direction of interfaces signals.
       
        Taking the analogy of a physical connector, modport permits to differentiate a
        male and female plug, give a polarity to the signals.
       
        ┊ interface bus_if;
        ┊   // the list of signals are preserved even with modport
        ┊   logic req;
        ┊   logic ack;
        ┊   logic[7:0] data;
        ┊ 
        ┊   modport mpController (
        ┊     output req,
        ┊     output data,
        ┊     input ack
        ┊   );
        ┊ 
        ┊   modport mpPeripheral (
        ┊     input req,
        ┊     input data,
        ┊     output ack
        ┊   );
        ┊ endinterface
       
        Then we have the choice to pass the entire interface to modules, or to pass
        only a modport that restricts the direction for extra safety:
       
        ┊ module lighter (
        ┊   input logic clk,
        ┊   bus_if.mpPeripheral bus,  // the modport is passed instead
        ┊   output logic[2:0] rgb_leds
        ┊ );
        ┊   assign bus.ack = bus.req;  // no syntax change here
        ┊   [...]
        ┊ endmodule
        ┊ 
        ┊ module top (
        ┊   input logic clk
        ┊ );
        ┊   bus_if bus ();  // instantiate the interface with all its modports
        ┊ 
        ┊   always @(posedge clk) begin
        ┊     if (bus.req) begin // accessing the interface signals directly
        ┊       bus.mpController.req <= 1;  // or through the modport
        ┊     end
        ┊   end
        ┊ 
        ┊   lighter peripheral1 (
        ┊     .clk(clk),
        ┊     .bus(bus.mpPeripheral)  // passing the modport for safety
        ┊   );
        ┊ endmodule
       
       Integrating external signals
       ────────────────────────────
        Interfaces can declare signals that always have the same direction. This is a
        convenience for clock and resets signals:
       
        ┊ interface bus_if (
        ┊   input clk,  // declared as input here, they will be input
        ┊   input rst   // signals everywhere in the modport
        ┊ );
        ┊   logic req;
        ┊   logic ack;
        ┊   logic[7:0] data;
        ┊ 
        ┊   modport mpController (
        ┊     input clk,  // they also need to be declared here for them
        ┊     input rst,  // to be reachable through the modport
        ┊     output req,
        ┊     output data,
        ┊     input ack
        ┊   );
        ┊ 
        ┊   modport mpPeripheral (
        ┊     input clk,  // to be declared in every modport they need to be
        ┊     input rst,  // used
        ┊     input req,
        ┊     input data,
        ┊     output ack
        ┊   );
        ┊ endinterface
       
       Integrating parameters
       ──────────────────────
        Like for modules, interfaces can have parameters, such as the size of some
        signals:
       
        ┊ interface bus_if #(
        ┊   parameter pDataSize = 8  // this parameter is optional
        ┊ ) (
        ┊   input clk  // external signals list are next, like for modules
        ┊ );
        ┊   logic[pDataSize-:0] data;  // note the use
        ┊   ...
        ┊ endinterface
        ┊ 
        ┊ module top ( ... );
        ┊   bus_if #( pDataSize = 8 ) bus;  // same syntax as modules
        ┊   ...
        ┊ endmodule
       
       Complete example
       ────────────────
        `bus_if.sv`:
       
        ┊ interface bus_if (
        ┊   input logic clk
        ┊ );
        ┊   logic req;
        ┊ 
        ┊   modport mpController (
        ┊     input clk,
        ┊     output req
        ┊   );
        ┊ 
        ┊   modport mpPeripheral (
        ┊     input clk,
        ┊     input req
        ┊   );
        ┊ endinterface
       
        `lighter.sv`:
       
        ┊ module lighter (
        ┊   bus_if.mpPeripheral bus,
        ┊   output logic led
        ┊ );
        ┊   always_ff @(posedge bus.clk) begin
        ┊     if (bus.req) begin
        ┊       led <= 1;
        ┊     end
        ┊   end
        ┊ endmodule
       
        `top.sv`:
       
        ┊ module top (
        ┊   input logic clk,
        ┊   output logic led // led
        ┊ );
        ┊ 
        ┊   // instantiate the bus interface
        ┊   bus_if bus ( .clk(clk) );
        ┊ 
        ┊   // default value for simulation only
        ┊   initial begin
        ┊     bus.req = 0;
        ┊   end
        ┊ 
        ┊   // issue a request over the bus
        ┊   always_ff @(posedge clk) begin
        ┊     // set a toggle train for demo
        ┊     bus.req <= !bus.req;
        ┊   end
        ┊ 
        ┊   // instantiate the module
        ┊   lighter peripheral0 (
        ┊     .bus(bus.mpPeripheral),
        ┊     .led(led)
        ┊   );
        ┊ endmodule
       
        `mSynthesis.sv`:
       
        ┊ module mSynthesis (
        ┊   output logic gpio_1
        ┊ );
        ┊   // Lattice way to setup the clock
        ┊   SB_HFOSC hfosc ( .CLKHFPU(1'b1), .CLKHFEN(1'b1), .CLKHF(clk) );
        ┊ 
        ┊   // instantiate the top module and bind the GPIO pins to it
        ┊   top top (
        ┊     .clk(clk),
        ┊     .led(gpio_1)
        ┊   );
        ┊ endmodule
       
        `simulation.cpp`
       
        ┊ #include "verilated.h"
        ┊ #include "verilated_vcd_c.h"
        ┊ #include "Vtop.h"
        ┊ 
        ┊ int
        ┊ main(int argc, char **argv)
        ┊ {
        ┊         Verilated::commandArgs(argc, argv);
        ┊         Verilated::traceEverOn(true);
        ┊ 
        ┊         Vtop *vsim = new Vtop;
        ┊         VerilatedVcdC *vcd = new VerilatedVcdC;
        ┊ 
        ┊         vsim->trace(vcd, 99);
        ┊         vcd->open("simulation.vcd");
        ┊ 
        ┊         vsim->eval();
        ┊         vcd->dump(0);
        ┊ 
        ┊         for (unsigned long long ns = 0; ns < 1000;) {
        ┊                 vsim->clk = 1;
        ┊                 vsim->eval();
        ┊                 vcd->dump(ns += 100);
        ┊ 
        ┊                 vsim->clk = 0;
        ┊                 vsim->eval();
        ┊                 vcd->dump(ns += 100);
        ┊         }
        ┊ 
        ┊         vcd->flush();
        ┊ }
       
        When calling Verilator:
       
        ┊ $ verilator --version
        ┊ Verilator 4.224 2022-06-19 rev v4.224
        ┊ 
        ┊ $ verilator -Wall --trace --sv -cc --top-module top top.sv lighter.sv bus_if.sv
        ┊ 
        ┊ $ make -C obj_dir -f Vtop.mk
        ┊ gmake: Entering directory '/home/josuah/example/obj_dir'
        ┊ /usr/bin/perl /usr/local/share/verilator/bin/verilator_includer -DVL_INCLUDE_OPT=include Vtop.cpp Vtop.cpp Vtop.cpp Vtop.cpp Vtop.cpp Vtop.cpp Vtop.cpp Vtop.cpp Vtop.cpp Vtop.cpp Vtop.cpp > Vtop.cpp
        ┊ c++  -I.  -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=1 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=1 -faligned-new -fbracket-depth=4096 -fcf-protection=none -Qunused-arguments -Wno-bool-operation -Wno-tautological-bitwise-compare -Wno-parentheses-equality -Wno-sign-compare -Wno-uninitialized -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable -Wno-shadow      -std=gnu++14 -Os -c -o Vtop.o Vtop.cpp
        ┊ echo "" > Vtop.verilator_deplist.tmp
        ┊ Archive ar -rcs Vtop.a Vtop.o
        ┊ rm Vtop.verilator_deplist.tmp
        ┊ gmake: Leaving directory '/home/josuah/example/obj_dir'
        ┊ 
        ┊ $ VERILATOR_INC=/usr/local/share/verilator/include
        ┊ 
        ┊ $ c++ -I${VERILATOR_INC} -Iobj_dir   -o simulation.elf simulation.cpp ${VERILATOR_INC}/verilated.cpp ${VERILATOR_INC}/verilated_vcd_c.cpp obj_dir/Vtop.a
        ┊ 
        ┊ $ ./simulation.elf
        ┊ 
        ┊ $ gtkwave simulation.vcd
       
        When calling yosys
       
        ┊ $ yosys --version
        ┊ Yosys 0.9+4081 (git sha1 UNKNOWN, c++ 13.0.0 -O2 -fPIC -Os)
        ┊ 
        ┊ $ yosys -p "read_verilog -sv mSynthesis.sv top.sv lighter.sv bus_if.sv; synth_ice40 -top mSynthesis -json synthesis.json"
        ┊ [a lot of output from yosys]
       
        Still (2022-07-06) not support from Icarus Verilog:
       
        ┊ $ iverilog -g2012 bus_if.sv lighter.sv mSynthesis.sv top.sv
        ┊ lighter.sv:2: syntax error
        ┊ lighter.sv:2: Errors in port declarations.
       
       Debugging interface syntax
       ──────────────────────────
        Using SystemVerilog constructs might require to add a command-line flag like
        `--sv`.
       
 (HTM)  Yosys {does not like} `default_nettype none` along with interfaces, you will
        have to disable it if you want to use interfaces, until the feature is
        implemented.
       
       Discussion
       ──────────
        SystemVerilog interfaces are fairly well supported by recent versions of
        Verilator (increasingly through 3.x and 4.x) and Yosys (I tested with
        0.9+4081).
       
 (HTM)  {Slightly less} with Icarus Verilog (iverilog).
       
        Some features might not be available on all versions, and observing the mailing
        list and bugtrackers of both project would help checking if a bug is from
        misuse of interfaces, or lack of support by the upstream tool.
       
        In every situation discussing about interfaces, the use-case was bundling these
        numerous master/slave bus signals present at the top of every module.
       
        Without `struct` or `interface`, these signals have to be written one by one on
        every module declaration or instantiation, and this takes-up a lot of visual
        space distracting from the real content.
       
        Interfaces was being discouraged by {LowRISC coding style} for a {bug encountered in 2018} 
 (HTM)  • {LowRISC coding style}
 (HTM)  • {bug encountered in 2018}
        n one power-domain checking tool.
       
        ┊ This may have gotten a lot better since. --  `@tjaychen` in June 2021
       
        This at least works fairly well for the synthesis and simulation path today!
       
       Links
       ─────
 (HTM)  {https://www.chipverify.com/systemverilog/systemverilog-struct}
       
 (HTM)  {https://www.chipverify.com/systemverilog/systemverilog-interface}
       
 (HTM)  {https://www.chipverify.com/systemverilog/systemverilog-modport}
       
 (HTM)  {https://www.bilibili.com/video/BV1uS4y1z7JN} (video)
       
 (HTM)  {https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md}