{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}