A Bus Interface: APB and AXI4-Lite

Write one APB property module that verifies either side, see why flipping its verbs is not free, and run the same idea over the five AXI4-Lite channels.

Lesson
F8.5
Time
25 min
Runs on
SymbiYosys
Check
4 questions

A team proved an APB completer whose bench bounded the wait states at zero, because their completer never waited. Later they flipped every verb to verify a requester, ran it, and got PASS in a second. That requester changed PADDR during wait states, and the flipped bound had promised there would be none.

One Property Module, Two Benchesapb_fv holds every APB rule as a wire. CPL = 1: the design is the completer, requester rules are assumed and completer rules asserted. CPL = 0 flips every verb.
One property module, two benchesapb_fv holds every APB rule as a wire. CPL = 1: the design is the completer, requester rules are assumed and completer rules asserted. CPL = 0 flips every verb. component Free requesterinputs completer bench:PSEL, PENABLE,PADDR ... chosen bythe solver component apb_fv, CPL = 1 requester rulesassumed, wait boundasserted component apb_cpl_f the completer undertest component Free completerinputs requester bench:PREADY chosen bythe solver component apb_fv, CPL = 0 requester rulesasserted, wait boundassumed component apb_req_f the requester undertest assumeassertassumeassert
Read the diagram as text
  • Free requester inputs: completer bench: PSEL, PENABLE, PADDR ... chosen by the solver
  • apb_fv, CPL = 1: requester rules assumed, wait bound asserted
  • apb_cpl_f: the completer under test
  • Free completer inputs: requester bench: PREADY chosen by the solver
  • apb_fv, CPL = 0: requester rules asserted, wait bound assumed
  • apb_req_f: the requester under test
  • Free requester inputs → apb_fv, CPL = 1: assume
  • apb_fv, CPL = 1 → apb_cpl_f: assert
  • Free completer inputs → apb_fv, CPL = 0: assume
  • apb_fv, CPL = 0 → apb_req_f: assert

The text of each rule never changes. What changes is who owes it, and so which verb the tool uses.

Every Rule Has a Side

Rule (IHI 0024E)Owed byWire in apb_fvCompleter benchRequester bench
SETUP lasts one cycle and ACCESS follows (4.1)requesterr_accessassumeassert
PENABLE only after SETUP (3.1, 4.1)requesterr_enableassumeassert
PADDR, PWRITE, PWDATA, PSTRB stable until PREADY (3.1.2)requesterr_stableassumeassert
No strobes on a read (3.2)requesterr_strobeassumeassert
At most MAXW wait states (a bounded response)completerc_waitassertassume
PREADY may take any value while PENABLE is LOW (3.1.2)nobodyno rule--

The rules P3.2 and P3.8 teach in simulation. The last row is a permission: a checker that flags it raises a false alarm.

A protocol property file lists every rule once and says who owes it. On the completer bench the requester's rules are assumptions and the completer's are assertions; on the requester bench the verbs swap. Here a parameter picks the verb for each wire.

PREADY may be high while PENABLE is low. A completer with a fixed two-cycle access can tie it high. Adding "no PREADY outside ACCESS" to the bench is a classic over-strict checker.

The Completer Bench

Completer bench: PRDATA arrives one cycle late
0123clkfreepselfreepenablefreepaddr040preadyprdata0000000041504234SETUP, read of IDcompletion, PRDATA still 0

Measured with smtbmc yices: FAIL at reported step 3, 0.5 s. The ID value appears a cycle after the transfer ended.

apb_cpl_f, completer benchMeasured result
No wait states, MAXW 0, bmc 12BMC PASS to depth 12, 0.6 s
Same, prove k = 4UNKNOWN at k = 4 (base case passed, induction step failed), 0.5 s
Same, abc pdrProve PASS (abc pdr), 0.6 s
P3 late_rdata fault, bmc 12FAIL at reported step 3, 0.5 s
Two wait states, MAXW 2, bmc 12BMC PASS to depth 12, 0.6 s
ready_in_setup (legal), bmc 12BMC PASS to depth 12, 0.6 s
ready_in_setup with the over-strict lineFAIL at reported step 2, 0.6 s

Covers with two wait states: a waited access at step 3, an error response at step 5, back-to-back transfers at step 6. The completer has 66 flops.

The data check belongs to this completer, not to the protocol: a read returns the register. From an arbitrary start, k-induction cannot tie the bench's copy of CTRL to the design's. PDR closes it.

A Flipped File Needs Its Own Covers

Requester bench: PADDR moves while the access waits
012345clkfreestartfreeaddr080pselpenablefreepreadypaddr080ACCESS, PREADY lowPADDR changed

Measured: FAIL at reported step 5, 0.6 s, on r_stable.

apb_req_f with BUG_ADDR_CHANGEAssertions (bmc 12)Covers
MAXW 0, copied from the completer benchBMC PASS to depth 12waited access unreached; completion at step 4
MAXW 3, the system's real boundFAIL at reported step 5waited access at step 4

The fixed requester with MAXW 3: BMC PASS to depth 12; prove k = 4: Prove PASS (k = 4).

On the requester bench the completer's wait bound is an assumption. Set to the old completer's zero, it says every access completes at once, and the bug needs a wait state. The cover for a waited access comes back unreached and gives the hollow PASS away (F3.2).

A flipped file needs its own covers, and every flipped assertion must be checked as an assumption: is it true of every partner, or only of the one you proved?

AXI4-Lite: Five Handshakes

ChannelSenderVALID holds until READYPayload stable while waiting
AWmanagerassumeassume
Wmanagerassumeassume
ARmanagerassumeassume
Bsubordinateassertassert
Rsubordinateassertassert

IHI 0022L A2.3. Plus two response rules, asserted: no BVALID before an accepted address and data, no RVALID before an accepted read address (A2.3.2). The block is P2.15's register block, 142 flops in this formal copy.

AXI4-Lite: RVALID falls before RREADY
01234clkfreearvalidarreadyrvalidfreerreadyRVALID, RREADY lowRVALID dropped

Measured: FAIL at reported step 4, 0.6 s.

Every channel has the same two rules. The manager's three are assumptions here; the subordinate's two are assertions. Correct block: BMC PASS to depth 12, and abc pdr Prove PASS (abc pdr). The covers reach a stalled write response, a stalled read and data before address.

Asked in Interviews

QuestionAnswer
How do you decide a failure is the environment's fault on a bus?Check the rule table: if the free input breaks a rule the other side owes, an assumption is missing. Cite the clause.
How does one property file serve both sides?A parameter picks the verb per rule. Each side adds its own covers and checks every flipped assertion as an assumption.
What are the AXI handshake rules?VALID holds until READY; the payload is stable while waiting; a sender never waits for READY before raising VALID.

Try It

The lab task: the requester bench. The twins run the hollow bench, its covers, the completer bench and the AXI4-Lite block.
[options]
mode bmc
depth 12
[engines]
smtbmc yices
[script]
read_verilog -formal -sv design.sv
prep -top req_top
[files]
design.sv
How the rule file is often written in industry: one macro per side. The engine here runs the module below, where a parameter picks the verb.
`define REQ_RULE(p) if (CPL) assume property (p); else assert property (p)
`REQ_RULE(@(posedge clk) disable iff (!presetn) psel && !penable |=> psel && penable);
`REQ_RULE(@(posedge clk) disable iff (!presetn) psel && !(penable && pready) |=> $stable({paddr, pwrite, pwdata, pstrb}));
`REQ_RULE(@(posedge clk) disable iff (!presetn) psel && !pwrite |-> pstrb == 0);
apb_fv as the engine runs it.
module apb_fv #(parameter CPL = 1, MAXW = 0)(input clk, presetn, psel, penable, pwrite,
  input [3:0] paddr, input [31:0] pwdata, input [3:0] pstrb, input pready);
  reg setup_q = 0, wait_q = 0, done_q = 0;          // last cycle: SETUP, a waited ACCESS, a completion
  reg [3:0] paddr_q = 0, pstrb_q = 0;
  reg [31:0] pwdata_q = 0;
  reg pwrite_q = 0;
  reg [2:0] waits = 0;                               // wait states so far in this access
  always @(posedge clk) begin
    setup_q <= presetn && psel && !penable;
    wait_q  <= presetn && psel && penable && !pready;
    done_q  <= presetn && psel && penable && pready;
    paddr_q <= paddr; pwdata_q <= pwdata; pstrb_q <= pstrb; pwrite_q <= pwrite;
    waits <= presetn && psel && penable && !pready ? waits + 3'd1 : 3'd0;
  end
  // Requester rules (IHI 0024E 3.1, 3.2, 4.1)
  wire r_access = !(setup_q || wait_q) || psel && penable;                // SETUP and a waited ACCESS go on to ACCESS
  wire r_enable = !penable || psel && (setup_q || wait_q);                // PENABLE only after SETUP
  wire r_stable = !(setup_q || wait_q) || paddr == paddr_q && pwrite == pwrite_q && pwdata == pwdata_q && pstrb == pstrb_q;
  wire r_strobe = !(psel && !pwrite) || pstrb == 4'd0;                    // no strobes on a read
  // Completer rule: at most MAXW wait states
  wire c_wait = !(psel && penable && !pready) || waits < MAXW;
  always @(posedge clk) if (presetn) begin
    if (CPL) begin
      assume(r_access); assume(r_enable); assume(r_stable); assume(r_strobe);
      assert(c_wait);
    end else begin
      assert(r_access); assert(r_enable); assert(r_stable); assert(r_strobe);
      assume(c_wait);
    end
  end
endmodule
  1. Assign the sides

    Before opening the code, mark each row of the rule table: who owes it?

  2. The hollow bench

    Run the MAXW 0 twin: BMC PASS to depth 12. Run its covers and find the one that is unreached.

  3. The real bound

    Run as loaded with MAXW 3: FAIL at reported step 5. Walk the trace to the cycle PADDR moves.

  4. AXI4-Lite

    Run the AXI4-Lite bug twin and name the rule that fired.

design.sv

props.sv

Use assert property (@(posedge clk) ...);. Leave blank for immediate assertions in the design.

Inputs are free unless constrained. Run to find a counterexample or check a proof.

Model: one positive-edge clock, binary data. Sampled history requires declaration-initialized variables; primary-input history is unsupported.

SBY summary and log

Check Yourself

Predict the outputThe requester bench on apb_req_f with BUG_ADDR_CHANGE and MAXW 0 (every access completes at once), mode bmc, depth 12, smtbmc yices, reset assumed in step 0. The verdict is:
module apb_req_f(input clk, presetn, start, wr, input [3:0] addr, input [31:0] data, input pready,
  output reg psel = 0, output reg penable = 0, output reg pwrite = 0, output reg [3:0] paddr = 0,
  output reg [31:0] pwdata = 0, output [3:0] pstrb);
  assign pstrb = pwrite ? 4'hF : 4'h0;
  always @(posedge clk)
    if (!presetn) begin psel <= 0; penable <= 0; end
    else if (!psel) begin
      if (start) begin psel <= 1; pwrite <= wr; paddr <= addr; pwdata <= data; end   // SETUP
    end else if (!penable) penable <= 1;                                           // ACCESS
    else if (pready) begin                                                         // done
      penable <= 0; psel <= start;                                                 // next SETUP at once, or IDLE
      if (start) begin pwrite <= wr; paddr <= addr; pwdata <= data; end
    end
    else paddr <= addr;   // bug: the address follows the input while the access waits
endmodule
module apb_fv #(parameter CPL = 1, MAXW = 0)(input clk, presetn, psel, penable, pwrite,
  input [3:0] paddr, input [31:0] pwdata, input [3:0] pstrb, input pready);
  reg setup_q = 0, wait_q = 0, done_q = 0;          // last cycle: SETUP, a waited ACCESS, a completion
  reg [3:0] paddr_q = 0, pstrb_q = 0;
  reg [31:0] pwdata_q = 0;
  reg pwrite_q = 0;
  reg [2:0] waits = 0;                               // wait states so far in this access
  always @(posedge clk) begin
    setup_q <= presetn && psel && !penable;
    wait_q  <= presetn && psel && penable && !pready;
    done_q  <= presetn && psel && penable && pready;
    paddr_q <= paddr; pwdata_q <= pwdata; pstrb_q <= pstrb; pwrite_q <= pwrite;
    waits <= presetn && psel && penable && !pready ? waits + 3'd1 : 3'd0;
  end
  // Requester rules (IHI 0024E 3.1, 3.2, 4.1)
  wire r_access = !(setup_q || wait_q) || psel && penable;                // SETUP and a waited ACCESS go on to ACCESS
  wire r_enable = !penable || psel && (setup_q || wait_q);                // PENABLE only after SETUP
  wire r_stable = !(setup_q || wait_q) || paddr == paddr_q && pwrite == pwrite_q && pwdata == pwdata_q && pstrb == pstrb_q;
  wire r_strobe = !(psel && !pwrite) || pstrb == 4'd0;                    // no strobes on a read
  // Completer rule: at most MAXW wait states
  wire c_wait = !(psel && penable && !pready) || waits < MAXW;
  always @(posedge clk) if (presetn) begin
    if (CPL) begin
      assume(r_access); assume(r_enable); assume(r_stable); assume(r_strobe);
      assert(c_wait);
    end else begin
      assert(r_access); assert(r_enable); assert(r_stable); assert(r_strobe);
      assume(c_wait);
    end
  end
endmodule

module req_top(input clk, presetn, start, wr, input [3:0] addr, input [31:0] data, input pready);
  wire psel, penable, pwrite;
  wire [3:0] paddr, pstrb;
  wire [31:0] pwdata;
  apb_req_f dut(.clk(clk), .presetn(presetn), .start(start), .wr(wr), .addr(addr), .data(data), .pready(pready),
    .psel(psel), .penable(penable), .pwrite(pwrite), .paddr(paddr), .pwdata(pwdata), .pstrb(pstrb));
  apb_fv #(.CPL(0), .MAXW(0)) fv(.clk(clk), .presetn(presetn), .psel(psel), .penable(penable), .pwrite(pwrite),
    .paddr(paddr), .pwdata(pwdata), .pstrb(pstrb), .pready(pready));
`ifdef FORMAL
  always @(posedge clk) begin
    if ($initstate) begin assume(!presetn); end else begin assume(!!presetn); end
  end
`endif
endmodule
ConceptWhich line is wrong for an APB bench?
ConceptWhy does flipping assumes and asserts not verify the other side for free?
Read the waveformIn the requester trace, in which step does PADDR change, and which rule fires?
Requester bench: PADDR moves while the access waits
012345clkfreestartfreeaddr080pselpenablefreepreadypaddr080ACCESS, PREADY lowPADDR changed

Measured: FAIL at reported step 5, 0.6 s, on r_stable.

Remember

  • A protocol file lists each rule once with its owner; a parameter picks assume or assert.
  • PREADY high while PENABLE is low is legal; a checker that flags it is over-strict.
  • A flipped assertion is an assumption on the new bench: check it, and cover what it might exclude.
  • AXI4-Lite is five handshakes with the same two rules, plus response-after-request rules.