The last capstone checks a UART transmitter, the same kind of block whose timing the Static Timing Analysis capstone analyzes. Where STA asks whether the gates are fast enough, formal asks whether the logic sends the right frame: a start bit, the data bits in the right order, a stop bit, each for the right number of clock cycles. The design is parameterized so that the check fits the free lab depth, and the full-size version runs with PRO.

11 min read | Intermediate

What You'll Learn

  • Turn a serial protocol frame into properties with a reference timeline
  • Check every data bit with a symbolic bit index
  • Find a bit-order bug in a transmitter
  • Scale the check from a small frame to the full UART frame

The Frame

A UART line is high when idle. A frame starts with a start bit of 0, then sends the data bits, least significant first, then a stop bit of 1. Each bit lasts a fixed number of clock cycles, set by the baud rate. The transmitter here has two parameters: N data bits and BAUD clock cycles per bit. A real UART uses 8 data bits and a BAUD of tens or hundreds; the check uses small values.

The Design


module uart_tx #(parameter N = 4, parameter BAUD = 1) (input clk, input rst, input start, input [N-1:0] data,
                                                 output reg tx, output busy);
  reg [N-1:0] sh;
  reg [4:0]   bitn;          // 0 = start bit, 1..N = data bits, N+1 = stop bit
  reg [7:0]   cnt;           // clocks left in the current bit
  reg         active;
  assign busy = active;
  always @(posedge clk)
    if (rst) begin
      active <= 1'b0; tx <= 1'b1;
    end else if (!active) begin
      tx <= 1'b1;
      if (start) begin active <= 1'b1; sh <= data; bitn <= 5'd0; cnt <= BAUD - 1; tx <= 1'b0; end
    end else if (cnt != 0) begin
      cnt <= cnt - 8'd1;
    end else begin
      cnt <= BAUD - 1;
      bitn <= bitn + 5'd1;
      if (bitn < N)       begin tx <= sh[N-1]; sh <= sh << 1; end   // bug: sends MSB first, the spec says LSB first
      else if (bitn == N) tx <= 1'b1;                               // stop bit
      else                begin active <= 1'b0; tx <= 1'b1; end
    end
endmodule

The shift register sends the most significant bit first. Many serial protocols do, such as SPI and I2C, which makes this an easy mistake when code is reused; UART sends the least significant bit first.

The Properties

A reference timeline t counts clock cycles from the start of the frame, and ref_d remembers the data. A symbolic bit index k, as in the symbolic values article, checks every data bit with one assertion:


`ifdef FORMAL
  // ... reset assumption ...
  // the line is high whenever the transmitter is idle
  always @(*) if (!init && !busy) assert (tx == 1'b1);
  // reference model: count clocks since the start of the frame, and remember the data
  reg [7:0] t = 8'd0; reg [N-1:0] ref_d; reg on = 1'b0;
  always @(posedge clk)
    if (rst) on <= 1'b0;
    else if (!busy && start) begin on <= 1'b1; t <= 8'd0; ref_d <= data; end
    else if (on) begin t <= t + 8'd1; if (!busy) on <= 1'b0; end
  // a symbolic data bit index: the tool picks which bit to check
  (* anyconst *) reg [4:0] k;
  always @(*) assume (k < N);
  // start bit, data bit k, stop bit, each held for BAUD cycles
  always @(*) if (!init && on && busy && t < BAUD) assert (tx == 1'b0);
  always @(*) if (!init && on && busy && t >= BAUD * (k + 1) && t < BAUD * (k + 2)) assert (tx == ref_d[k]);
  always @(*) if (!init && on && busy && t >= BAUD * (N + 1) && t < BAUD * (N + 2)) assert (tx == 1'b1);
  // the frame takes exactly N + 2 bit times
  always @(posedge clk) if (!init && on && $past(busy) && !busy) assert (t == BAUD * (N + 2));
`endif

In SVA, the data-bit rule is a sequence: after the start, ##(BAUD * (k + 1)) cycles, tx equals data bit k for BAUD cycles. The reference timeline is the lab form of that sequence.

The Counterexample

With N = 4 and BAUD = 1, the run fails at step 3. The tool starts a frame in step 1 with data = 1, so only bit 0 is set, and checks k = 0. The start bit appears in step 2, as it should. In step 3, bit 0 should be on the line, a 1, but the transmitter sends bit 3, a 0. Change the shift to tx <= sh[0]; sh <= sh >> 1; and every check passes.

Quick Check

Q: Why did the tool choose data = 1 and k = 0?

Show Answer

That is one of the shortest ways to make the first data bit wrong: with only bit 0 set, LSB-first sends a 1 and MSB-first sends a 0. Any data whose bit 0 differs from bit 3 would do; the solver picks one.

Scaling Up

The fixed transmitter passes on the lab engine for:

NBAUDDepthResult
4110pass, under a second
4216pass, under a second
8114pass, under a second
8450pass, about 2 seconds

The last row is a full 8-bit frame with 4 cycles per bit, 40 cycles long. A meant-to-fail check that a frame never completes fails at step 9 for N = 4, BAUD = 1, which shows that the depth of 10 really covers a whole frame. For a real baud divider of, say, 434 clocks per bit, the timing logic is the same code with a larger parameter; prove it with the small values and check the divider separately.

Quick Check

Q: What depth does a full frame need for N = 8 and BAUD = 4?

Show Answer

The frame is 10 bit times of 4 cycles, 40 cycles, plus the reset, the start request and the reporting step: a little over 40. The table uses 50.

Running It in ChipVerify Lab

The example is the buggy transmitter with N = 4 and BAUD = 1, which fits the free depth. With PRO, raise the parameters and the depth as in the table.

Common Mistakes

Mistake #1: Checking only "the frame starts and ends"

Start and stop bits can be perfect while the data is reversed. Check every data bit, which the symbolic index does with one assertion.

Mistake #2: Running the real baud rate

Hundreds of cycles per bit make the frame thousands of cycles long. Use a small BAUD for the frame logic and check the divider on its own.

Mistake #3: Forgetting the idle line

A transmitter that drives the line low between frames would look like a start bit to the receiver. "The line is high when idle" is a one-line property; include it.

Practice Exercise

Challenge: Fix the Bit Order and Scale Up

Open the example and run it. Fix the bit order. Then run with N = 8 and, with PRO, with BAUD = 4 at depth 50. Finally add a meant-to-fail check that a frame completes, and note the step.

Hint

LSB first means sending sh[0] and shifting right.

Solution

The fix is if (bitn < N) begin tx <= sh[0]; sh <= sh >> 1; end. All checks pass for the sizes in the table. The meant-to-fail check always @(posedge clk) if (!init) assert (!(on && $past(busy) && !busy)); fails at step 9 for N = 4, BAUD = 1: a frame requested in step 1 ends 6 bit times later.

Open the ready-made example in the lab and run the formal check from the formal panel. The free tier allows a depth of up to 10 cycles, and PRO allows up to 100.

Summary

  • A serial frame becomes properties with a reference timeline and a symbolic bit index.
  • A transmitter that sent the MSB first failed at step 3 with a one-bit data word.
  • The fixed transmitter passed from 4 data bits at one clock per bit up to a full 8-bit frame at 4 clocks per bit.
  • Use small parameters for the frame logic; check the baud divider separately.
Did you find what you were looking for?

Quiz

No quiz questions available for this article.