summaryrefslogtreecommitdiff
path: root/rtl/wb2axip/axisswitch.v
diff options
context:
space:
mode:
authorAlejandro Soto <alejandro@34project.org>2024-03-06 02:38:24 -0600
committerAlejandro Soto <alejandro@34project.org>2024-03-06 02:38:24 -0600
commit3038edc09a2eb15762f2e58533f429489107520b (patch)
treef7a45e424d39e6fef0d59e329c1bf6ea206e2886 /rtl/wb2axip/axisswitch.v
parent3b62399f92e9faa2602ac30865e5fc3c7c4e12b8 (diff)
rtl/wb2axip: add to version control
Diffstat (limited to 'rtl/wb2axip/axisswitch.v')
-rw-r--r--rtl/wb2axip/axisswitch.v631
1 files changed, 631 insertions, 0 deletions
diff --git a/rtl/wb2axip/axisswitch.v b/rtl/wb2axip/axisswitch.v
new file mode 100644
index 0000000..bcae4a3
--- /dev/null
+++ b/rtl/wb2axip/axisswitch.v
@@ -0,0 +1,631 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// Filename: axisswitch.v
+// {{{
+// Project: WB2AXIPSP: bus bridges and other odds and ends
+//
+// Purpose: Switch from among several AXI streams based upon an AXI-lite
+// controlled index. All streams must have the same width.
+// The switch will use TLAST to guarantee that it will not change
+// mid-packet. If TLAST is unused for a particular input, simply set it
+// to 1'b1.
+//
+// Creator: Dan Gisselquist, Ph.D.
+// Gisselquist Technology, LLC
+//
+////////////////////////////////////////////////////////////////////////////////
+// }}}
+// Copyright (C) 2020-2024, Gisselquist Technology, LLC
+// {{{
+//
+// This file is part of the WB2AXIP project.
+//
+// The WB2AXIP project contains free software and gateware, licensed under the
+// Apache License, Version 2.0 (the "License"). You may not use this project,
+// or this file, except in compliance with the License. You may obtain a copy
+// of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+//
+`default_nettype none
+// }}}
+module axisswitch #(
+ // {{{
+ //
+ // Size of the AXI-lite bus. These are fixed, since 1) AXI-lite
+ // is fixed at a width of 32-bits by Xilinx def'n, and 2) since
+ // we only ever have a single configuration words.
+ parameter C_AXI_ADDR_WIDTH = 2,
+ localparam C_AXI_DATA_WIDTH = 32,
+ //
+ parameter NUM_STREAMS = 4,
+ parameter C_AXIS_DATA_WIDTH = 32,
+ parameter [0:0] OPT_LOWPOWER = 0
+ // }}}
+ ) (
+ // {{{
+ input wire S_AXI_ACLK,
+ input wire S_AXI_ARESETN,
+ // AXI-Lite control
+ // {{{
+ input wire S_AXI_AWVALID,
+ output wire S_AXI_AWREADY,
+ input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_AWADDR,
+ input wire [2:0] S_AXI_AWPROT,
+ //
+ input wire S_AXI_WVALID,
+ output wire S_AXI_WREADY,
+ input wire [C_AXI_DATA_WIDTH-1:0] S_AXI_WDATA,
+ input wire [C_AXI_DATA_WIDTH/8-1:0] S_AXI_WSTRB,
+ //
+ output wire S_AXI_BVALID,
+ input wire S_AXI_BREADY,
+ output wire [1:0] S_AXI_BRESP,
+ //
+ input wire S_AXI_ARVALID,
+ output wire S_AXI_ARREADY,
+ input wire [C_AXI_ADDR_WIDTH-1:0] S_AXI_ARADDR,
+ input wire [2:0] S_AXI_ARPROT,
+ //
+ output wire S_AXI_RVALID,
+ input wire S_AXI_RREADY,
+ output wire [C_AXI_DATA_WIDTH-1:0] S_AXI_RDATA,
+ output wire [1:0] S_AXI_RRESP,
+ // }}}
+ // AXI stream inputs to be switched
+ // {{{
+ input wire [NUM_STREAMS-1:0] S_AXIS_TVALID,
+ output wire [NUM_STREAMS-1:0] S_AXIS_TREADY,
+ input wire [NUM_STREAMS*C_AXIS_DATA_WIDTH-1:0] S_AXIS_TDATA,
+ input wire [NUM_STREAMS-1:0] S_AXIS_TLAST,
+ // }}}
+ // AXI stream output result
+ // {{{
+ output reg M_AXIS_TVALID,
+ input wire M_AXIS_TREADY,
+ output reg [C_AXIS_DATA_WIDTH-1:0] M_AXIS_TDATA,
+ output reg M_AXIS_TLAST
+ // }}}
+ // }}}
+ );
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Register/wire signal declarations
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ localparam LGNS = $clog2(NUM_STREAMS);
+ localparam ADDRLSB = $clog2(C_AXI_DATA_WIDTH)-3;
+
+ wire i_reset = !S_AXI_ARESETN;
+
+ wire axil_write_ready;
+ wire [0:0] awskd_addr; // UNUSED
+ //
+ wire [C_AXI_DATA_WIDTH-1:0] wskd_data;
+ wire [C_AXI_DATA_WIDTH/8-1:0] wskd_strb;
+ reg axil_bvalid;
+ //
+ wire axil_read_ready;
+ wire [0:0] arskd_addr; // UNUSED
+ reg [C_AXI_DATA_WIDTH-1:0] axil_read_data;
+ reg axil_read_valid;
+
+ reg [LGNS-1:0] r_index;
+ wire [31:0] wskd_index;
+
+ genvar gk;
+ reg [NUM_STREAMS-1:0] skd_switch_ready;
+ reg [LGNS-1:0] switch_index;
+ wire [C_AXIS_DATA_WIDTH-1:0] skd_data [0:NUM_STREAMS-1];
+ wire [NUM_STREAMS-1:0] skd_valid, skd_last;
+ reg mid_packet, r_mid_packet;
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // AXI-lite signaling
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ //
+ // Write signaling
+ //
+ // {{{
+
+ wire awskd_valid, wskd_valid;
+
+ skidbuffer #(.OPT_OUTREG(0),
+ .OPT_LOWPOWER(OPT_LOWPOWER), .DW(1))
+ axilawskid(//
+ .i_clk(S_AXI_ACLK), .i_reset(i_reset),
+ .i_valid(S_AXI_AWVALID), .o_ready(S_AXI_AWREADY),
+ .i_data(1'b0),
+ .o_valid(awskd_valid), .i_ready(axil_write_ready),
+ .o_data(awskd_addr));
+
+ skidbuffer #(.OPT_OUTREG(0),
+ .OPT_LOWPOWER(OPT_LOWPOWER),
+ .DW(C_AXI_DATA_WIDTH+C_AXI_DATA_WIDTH/8))
+ axilwskid(//
+ .i_clk(S_AXI_ACLK), .i_reset(i_reset),
+ .i_valid(S_AXI_WVALID), .o_ready(S_AXI_WREADY),
+ .i_data({ S_AXI_WDATA, S_AXI_WSTRB }),
+ .o_valid(wskd_valid), .i_ready(axil_write_ready),
+ .o_data({ wskd_data, wskd_strb }));
+
+ assign axil_write_ready = awskd_valid && wskd_valid
+ && (!S_AXI_BVALID || S_AXI_BREADY);
+
+ initial axil_bvalid = 0;
+ always @(posedge S_AXI_ACLK)
+ if (i_reset)
+ axil_bvalid <= 0;
+ else if (axil_write_ready)
+ axil_bvalid <= 1;
+ else if (S_AXI_BREADY)
+ axil_bvalid <= 0;
+
+ assign S_AXI_BVALID = axil_bvalid;
+ assign S_AXI_BRESP = 2'b00;
+ // }}}
+
+ //
+ // Read signaling
+ //
+ // {{{
+
+ wire arskd_valid;
+
+ skidbuffer #(.OPT_OUTREG(0),
+ .OPT_LOWPOWER(OPT_LOWPOWER),
+ .DW(1))
+ axilarskid(//
+ .i_clk(S_AXI_ACLK), .i_reset(i_reset),
+ .i_valid(S_AXI_ARVALID), .o_ready(S_AXI_ARREADY),
+ .i_data(1'b0),
+ .o_valid(arskd_valid), .i_ready(axil_read_ready),
+ .o_data(arskd_addr));
+
+ assign axil_read_ready = arskd_valid
+ && (!axil_read_valid || S_AXI_RREADY);
+
+ initial axil_read_valid = 1'b0;
+ always @(posedge S_AXI_ACLK)
+ if (i_reset)
+ axil_read_valid <= 1'b0;
+ else if (axil_read_ready)
+ axil_read_valid <= 1'b1;
+ else if (S_AXI_RREADY)
+ axil_read_valid <= 1'b0;
+
+ assign S_AXI_RVALID = axil_read_valid;
+ assign S_AXI_RDATA = axil_read_data;
+ assign S_AXI_RRESP = 2'b00;
+ // }}}
+
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // AXI-lite register logic
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ // apply_wstrb(old_data, new_data, write_strobes)
+ // r_index, (wskd_index)
+ // {{{
+ assign wskd_index = apply_wstrb(
+ { {(C_AXI_DATA_WIDTH-LGNS){1'b0}}, r_index },
+ wskd_data, wskd_strb);
+
+ // r_index
+ initial r_index = 0;
+ always @(posedge S_AXI_ACLK)
+ if (i_reset)
+ r_index <= 0;
+ else if (axil_write_ready)
+ r_index <= wskd_index[LGNS-1:0];
+ // }}}
+
+ // axil_read_data
+ // {{{
+ initial axil_read_data = 0;
+ always @(posedge S_AXI_ACLK)
+ if (OPT_LOWPOWER && !S_AXI_ARESETN)
+ axil_read_data <= 0;
+ else if (!S_AXI_RVALID || S_AXI_RREADY)
+ begin
+ axil_read_data <= 0;
+ axil_read_data[LGNS-1:0] <= r_index;
+
+ if (OPT_LOWPOWER && !axil_read_ready)
+ axil_read_data <= 0;
+ end
+ // }}}
+
+ // function apply_wstrb
+ // {{{
+ function [C_AXI_DATA_WIDTH-1:0] apply_wstrb;
+ input [C_AXI_DATA_WIDTH-1:0] prior_data;
+ input [C_AXI_DATA_WIDTH-1:0] new_data;
+ input [C_AXI_DATA_WIDTH/8-1:0] wstrb;
+
+ integer k;
+ for(k=0; k<C_AXI_DATA_WIDTH/8; k=k+1)
+ begin
+ apply_wstrb[k*8 +: 8]
+ = wstrb[k] ? new_data[k*8 +: 8] : prior_data[k*8 +: 8];
+ end
+ endfunction
+ // }}}
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // The actual AXI switch
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ // Place a skidbuffer on every incoming stream input
+ // {{{
+ generate for(gk=0; gk<NUM_STREAMS; gk=gk+1)
+ begin
+ skidbuffer #(
+ // {{{
+ .OPT_OUTREG(0),
+ .OPT_LOWPOWER(OPT_LOWPOWER),
+ .DW(C_AXIS_DATA_WIDTH+1)
+ // }}}
+ ) skdswitch(//
+ // {{{
+ .i_clk(S_AXI_ACLK), .i_reset(i_reset),
+ .i_valid(S_AXIS_TVALID[gk]),.o_ready(S_AXIS_TREADY[gk]),
+ .i_data({ S_AXIS_TDATA[gk*C_AXIS_DATA_WIDTH
+ +: C_AXIS_DATA_WIDTH], S_AXIS_TLAST[gk] }),
+ .o_valid(skd_valid[gk]), .i_ready(skd_switch_ready[gk]),
+ .o_data({ skd_data[gk], skd_last[gk] })
+ // }}}
+ );
+
+
+ end endgenerate
+ // }}}
+
+ // skd_switch_ready
+ // {{{
+ always @(*)
+ begin
+ skd_switch_ready = (1<<switch_index);
+ if (M_AXIS_TVALID && !M_AXIS_TREADY)
+ skd_switch_ready = 0;
+ if (!mid_packet && r_index != switch_index)
+ skd_switch_ready = 0;
+ end
+ // }}}
+
+ // mid_packet -- are we currently within a packet or not
+ // {{{
+ initial r_mid_packet = 0;
+ always @(posedge S_AXI_ACLK)
+ if (!S_AXI_ARESETN)
+ r_mid_packet <= 0;
+ else if (M_AXIS_TVALID)
+ r_mid_packet <= !M_AXIS_TLAST;
+
+ always @(*)
+ if (M_AXIS_TVALID)
+ mid_packet = !M_AXIS_TLAST;
+ else
+ mid_packet = r_mid_packet;
+ // }}}
+
+ // switch_index -- the current index of the skidbuffer switch
+ // {{{
+ initial switch_index = 0;
+ always @(posedge S_AXI_ACLK)
+ if (!S_AXI_ARESETN)
+ switch_index <= 0;
+ else if (!mid_packet)
+ switch_index <= r_index;
+ // }}}
+
+ // M_AXIS_TVALID
+ // {{{
+ initial M_AXIS_TVALID = 1'b0;
+ always @(posedge S_AXI_ACLK)
+ if (!S_AXI_ARESETN)
+ M_AXIS_TVALID <= 1'b0;
+ else if (!M_AXIS_TVALID || M_AXIS_TREADY)
+ M_AXIS_TVALID <= |(skd_valid & skd_switch_ready);
+ // }}}
+
+ // M_AXIS_TDATA, M_AXIS_TLAST
+ // {{{
+ initial M_AXIS_TDATA = 0;
+ initial M_AXIS_TLAST = 0;
+ always @(posedge S_AXI_ACLK)
+ if (!S_AXI_ARESETN && OPT_LOWPOWER)
+ begin
+ M_AXIS_TDATA <= 0;
+ M_AXIS_TLAST <= 0;
+ end else if (!M_AXIS_TVALID || M_AXIS_TREADY)
+ begin
+ M_AXIS_TDATA <= skd_data[switch_index];
+ M_AXIS_TLAST <= skd_last[switch_index];
+
+ if (OPT_LOWPOWER && (skd_valid | skd_switch_ready) == 0)
+ begin
+ M_AXIS_TDATA <= 0;
+ M_AXIS_TLAST <= 0;
+ end
+ end
+ // }}}
+
+ // }}}
+
+ // Make Verilator happy
+ // {{{
+ // Verilator lint_off UNUSED
+ wire unused;
+ assign unused = &{ 1'b0, S_AXI_AWPROT, S_AXI_ARPROT,
+ S_AXI_ARADDR[ADDRLSB-1:0], awskd_addr, arskd_addr,
+ S_AXI_AWADDR[ADDRLSB-1:0], wskd_index };
+ // Verilator lint_on UNUSED
+ // }}}
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+//
+// Formal properties used in verfiying this core
+// {{{
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+`ifdef FORMAL
+ reg f_past_valid;
+ initial f_past_valid = 0;
+ always @(posedge S_AXI_ACLK)
+ f_past_valid <= 1;
+
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // The AXI-lite control interface
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+ localparam F_AXIL_LGDEPTH = 4;
+ wire [F_AXIL_LGDEPTH-1:0] faxil_rd_outstanding,
+ faxil_wr_outstanding,
+ faxil_awr_outstanding;
+
+ faxil_slave #(
+ // {{{
+ .C_AXI_DATA_WIDTH(C_AXI_DATA_WIDTH),
+ .C_AXI_ADDR_WIDTH(C_AXI_ADDR_WIDTH),
+ .F_LGDEPTH(F_AXIL_LGDEPTH),
+ .F_AXI_MAXWAIT(2),
+ .F_AXI_MAXDELAY(2),
+ .F_AXI_MAXRSTALL(3),
+ .F_OPT_COVER_BURST(4)
+ // }}}
+ ) faxil(
+ // {{{
+ .i_clk(S_AXI_ACLK), .i_axi_reset_n(S_AXI_ARESETN),
+ //
+ .i_axi_awvalid(S_AXI_AWVALID),
+ .i_axi_awready(S_AXI_AWREADY),
+ .i_axi_awaddr( S_AXI_AWADDR),
+ .i_axi_awprot( S_AXI_AWPROT),
+ //
+ .i_axi_wvalid(S_AXI_WVALID),
+ .i_axi_wready(S_AXI_WREADY),
+ .i_axi_wdata( S_AXI_WDATA),
+ .i_axi_wstrb( S_AXI_WSTRB),
+ //
+ .i_axi_bvalid(S_AXI_BVALID),
+ .i_axi_bready(S_AXI_BREADY),
+ .i_axi_bresp( S_AXI_BRESP),
+ //
+ .i_axi_arvalid(S_AXI_ARVALID),
+ .i_axi_arready(S_AXI_ARREADY),
+ .i_axi_araddr( S_AXI_ARADDR),
+ .i_axi_arprot( S_AXI_ARPROT),
+ //
+ .i_axi_rvalid(S_AXI_RVALID),
+ .i_axi_rready(S_AXI_RREADY),
+ .i_axi_rdata( S_AXI_RDATA),
+ .i_axi_rresp( S_AXI_RRESP),
+ //
+ .f_axi_rd_outstanding(faxil_rd_outstanding),
+ .f_axi_wr_outstanding(faxil_wr_outstanding),
+ .f_axi_awr_outstanding(faxil_awr_outstanding)
+ // }}}
+ );
+
+ always @(*)
+ begin
+ assert(faxil_awr_outstanding== (S_AXI_BVALID ? 1:0)
+ +(S_AXI_AWREADY ? 0:1));
+ assert(faxil_wr_outstanding == (S_AXI_BVALID ? 1:0)
+ +(S_AXI_WREADY ? 0:1));
+
+ assert(faxil_rd_outstanding == (S_AXI_RVALID ? 1:0)
+ +(S_AXI_ARREADY ? 0:1));
+ end
+
+ always @(*)
+ assert(S_AXI_RDATA[C_AXI_DATA_WIDTH-1:LGNS] == 0);
+
+ always @(posedge S_AXI_ACLK)
+ if (f_past_valid && $past(S_AXI_ARESETN
+ && axil_read_ready))
+ begin
+ assert(S_AXI_RVALID);
+ assert(S_AXI_RDATA[LGNS-1:0] == $past(r_index));
+ end
+
+ //
+ // Check that our low-power only logic works by verifying that anytime
+ // S_AXI_RVALID is inactive, then the outgoing data is also zero.
+ //
+ always @(*)
+ if (OPT_LOWPOWER && !S_AXI_RVALID)
+ assert(S_AXI_RDATA == 0);
+
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // AXI Stream properties
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ generate for(gk=0; gk<NUM_STREAMS; gk=gk+1)
+ begin : S_STREAM_ASSUMPTIONS
+
+ always @(posedge S_AXI_ACLK)
+ if (!f_past_valid || $past(!S_AXI_ARESETN))
+ assume(S_AXIS_TVALID[gk] == 0);
+ else if ($past(S_AXIS_TVALID[gk] && !S_AXIS_TREADY[gk]))
+ begin
+ assume(S_AXIS_TVALID[gk]);
+ assume($stable(S_AXIS_TDATA[gk*C_AXIS_DATA_WIDTH +: C_AXIS_DATA_WIDTH]));
+ assume($stable(S_AXIS_TLAST[gk]));
+ end
+ end endgenerate
+
+ always @(posedge S_AXI_ACLK)
+ if (!f_past_valid || $past(!S_AXI_ARESETN))
+ assert(!M_AXIS_TVALID);
+ else if ($past(M_AXIS_TVALID && !M_AXIS_TREADY))
+ begin
+ assert(M_AXIS_TVALID);
+ assert($stable(M_AXIS_TDATA));
+ assert($stable(M_AXIS_TLAST));
+ end
+
+ always @(*)
+ if (OPT_LOWPOWER && !M_AXIS_TVALID)
+ begin
+ assert(M_AXIS_TDATA == 0);
+ assert(M_AXIS_TLAST == 0);
+ end
+
+ (* anyconst *) reg [LGNS-1:0] f_const_index;
+ (* anyconst *) reg [C_AXIS_DATA_WIDTH-1:0] f_never_data;
+ reg [LGNS-1:0] f_this_index;
+ reg [3:0] f_count, f_recount;
+ reg f_accepts, f_delivers;
+
+ always @(*)
+ assume(f_const_index < NUM_STREAMS);
+
+ // f_this_index
+ // {{{
+ initial f_this_index = 1'b0;
+ always @(posedge S_AXI_ACLK)
+ if (!S_AXI_ARESETN)
+ f_this_index <= 1'b0;
+ else if (!M_AXIS_TVALID || M_AXIS_TREADY)
+ f_this_index <= switch_index;
+
+ always @(*)
+ assert(f_this_index < NUM_STREAMS);
+
+ always @(*)
+ assert(switch_index < NUM_STREAMS);
+ // }}}
+
+ // f_count
+ // {{{
+ always @(*)
+ begin
+ f_accepts = S_AXIS_TVALID[f_const_index]
+ && S_AXIS_TREADY[f_const_index];
+ f_delivers = M_AXIS_TVALID && M_AXIS_TREADY
+ && f_this_index == f_const_index;
+ end
+
+ initial f_count = 0;
+ always @(posedge S_AXI_ACLK)
+ if (!S_AXI_ARESETN)
+ f_count <= 0;
+ else case( { f_accepts, f_delivers })
+ 2'b01: f_count <= f_count - 1;
+ 2'b10: f_count <= f_count + 1;
+ default: begin end
+ endcase
+ // }}}
+
+ // f_count induction
+ // {{{
+ always @(*)
+ begin
+ f_recount = 0;
+ if (!S_AXIS_TREADY[f_const_index])
+ f_recount = 1;
+ if (M_AXIS_TVALID && f_this_index == f_const_index)
+ f_recount = f_recount + 1;
+
+ assert(f_recount == f_count);
+ end
+ // }}}
+
+ // f_this_index induction
+ always @(*)
+ if (M_AXIS_TVALID && !M_AXIS_TLAST)
+ assert(f_this_index == switch_index);
+
+ // assume != f_never_data
+ // {{{
+ always @(posedge S_AXI_ACLK)
+ if (S_AXIS_TVALID[f_const_index])
+ assume({ S_AXIS_TDATA[f_const_index * C_AXIS_DATA_WIDTH +: C_AXIS_DATA_WIDTH], S_AXIS_TLAST[f_const_index] } != f_never_data);
+ // }}}
+
+ // Never data induction
+ // {{{
+ always @(*)
+ begin
+ if (skd_valid[f_const_index])
+ assert({ skd_data[f_const_index], skd_last[f_const_index] } != f_never_data);
+ if (M_AXIS_TVALID && f_this_index == f_const_index)
+ assert({ M_AXIS_TDATA, M_AXIS_TLAST } != f_never_data);
+ end
+ // }}}
+
+
+ // }}}
+ ////////////////////////////////////////////////////////////////////////
+ //
+ // Cover checks
+ // {{{
+ ////////////////////////////////////////////////////////////////////////
+ //
+ //
+
+ // While there are already cover properties in the formal property
+ // set above, you'll probably still want to cover something
+ // application specific here
+
+ // }}}
+`endif
+ // }}}
+endmodule