josuah.net | panoramix-labs.fr
Verilog lacks a way to build complex data types, akin to C's struct
.
SystemVerilog introduces a struct
feature, but it cannot declare input
s and output
s.
SystemVerilog introduces an interface
feature for use in module ports, 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
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.
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.
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
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
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
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.
Using SystemVerilog constructs might require to add a command-line flag like --sv
.
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.
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).
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 on 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!
https://www.chipverify.com/systemverilog/systemverilog-struct
https://www.chipverify.com/systemverilog/systemverilog-interface
https://www.chipverify.com/systemverilog/systemverilog-modport
https://www.bilibili.com/video/BV1uS4y1z7JN (video)
https://github.com/lowRISC/style-guides/blob/master/VerilogCodingStyle.md