Debug
Embedded logic analyzer
logic_analyzer
Pulse arm to start. Every clock the probe bits go into a DEPTH-sample buffer; the trigger fires on the first sample, after PRETRIGGER samples are kept, where (probe & trigger_mask) equals (trigger_value & trigger_mask), and a zero mask fires at once. After the trigger the buffer fills, then the whole window (PRETRIGGER samples before the trigger, the rest after) goes out on tx as one frame with a checksum (8N1, CLKS_PER_BIT clocks per bit). Connect tx to the board's USB-serial pin, or to a spare pin with a USB-serial adapter. The buffer uses block RAM on FPGAs; it is a debug aid, not part of the design to fabricate.
Instantiate it
Paste into your design and connect the signals.
logic_analyzer #(.WIDTH(8), .DEPTH(256), .PRETRIGGER(32), .CLKS_PER_BIT(234)) u_ila (
.clk(clk), .rst(rst),
.probe({state, valid, ready, data[4:0]}), .trigger_mask(8'h01), .trigger_value(8'h01),
.arm(button_pressed), .tx(uart_tx_pin), .armed(), .sending()
);Parameters
| Name | Default | Meaning |
|---|---|---|
| WIDTH | 8 | Probe bits, 1 to 32. |
| DEPTH | 256 | Samples per capture, a power of two from 4 to 4096. |
| PRETRIGGER | 32 | Samples kept from before the trigger (less than DEPTH). |
| CLKS_PER_BIT | 868 | Clock cycles per UART bit: clock frequency divided by baud rate (100 MHz / 115200 = 868). |
Ports
| Name | Direction | Width | Meaning |
|---|---|---|---|
| clk | input | 1 | Clock; everything changes on its rising edge. |
| rst | input | 1 | Synchronous reset, active high. |
| probe | input | WIDTH | The signals to record, sampled every clock. |
| trigger_mask | input | WIDTH | Which probe bits the trigger compares; all zero triggers at once. |
| trigger_value | input | WIDTH | The value those bits must have. |
| arm | input | 1 | Pulse for one cycle to start a capture. |
| tx | output | 1 | Serial line carrying the capture; idles high. |
| armed | output | 1 | High while recording or waiting for the trigger. |
| sending | output | 1 | High while the capture is being sent. |
Add it to a project
It is copied to ip/logic_analyzer/logic_analyzer.sv with a README (ip/logic_analyzer/README.md). When your design needs it, the Architect (new designs) or the Architect (changes) suggests it and asks before using it. Or add it yourself:
- In the workspace: the Tapeo Hub button in the project header, then Add to project.
- In a terminal (in the project folder):
$ tapeo hub add logic_analyzer - In VS Code: Tapeo: Add from Tapeo Hub in the Command Palette.
How it is verified
Before every release Tapeo checks this block: its self-checking testbench passes in Icarus Verilog, Verilator -Wall reports nothing, and Yosys synthesizes it (5164 generic cells at the default parameters). That shows it works for the tested cases; your copy is part of your project, and your own testbench checks it in your design.
Source
// Embedded logic analyzer: records `probe` every clock into a DEPTH-sample
// buffer, keeps PRETRIGGER samples from before the trigger, and sends the
// capture over its own UART (8N1, LSB first) as one frame that
// `tapeo fpga capture` turns into a VCD. Tapeo Hub (verified by Tapeo).
//
// Pulse `arm` to start. The trigger fires on the first sample, after at least
// PRETRIGGER samples, where (probe & trigger_mask) == (trigger_value & trigger_mask);
// a zero mask fires at once. Frame, in bytes:
// 0xA5 0x5A WIDTH DEPTH[7:0] DEPTH[15:8] PRETRIGGER[7:0] PRETRIGGER[15:8]
// DEPTH samples, oldest first, each ceil(WIDTH/8) bytes, least significant byte first
// one checksum byte: the XOR of every byte before it
module logic_analyzer #(
parameter integer WIDTH = 8, // probe bits, 1 to 32
parameter integer DEPTH = 256, // samples per capture, a power of two from 4 to 4096
parameter integer PRETRIGGER = 32, // samples kept from before the trigger, less than DEPTH
parameter integer CLKS_PER_BIT = 868 // clock frequency / baud rate, for example 100 MHz / 115200
) (
input logic clk,
input logic rst,
input logic [WIDTH-1:0] probe,
input logic [WIDTH-1:0] trigger_mask,
input logic [WIDTH-1:0] trigger_value,
input logic arm,
output logic tx,
output logic armed,
output logic sending
);
localparam integer ADDR_BITS = $clog2(DEPTH);
localparam integer SAMPLE_BYTES = (WIDTH + 7) / 8;
localparam integer HEADER_BYTES = 7;
localparam integer FRAME_BYTES = HEADER_BYTES + DEPTH * SAMPLE_BYTES + 1;
localparam integer COUNT_WIDTH = (CLKS_PER_BIT > 1) ? $clog2(CLKS_PER_BIT) : 1;
localparam logic [COUNT_WIDTH-1:0] LAST_TICK = COUNT_WIDTH'(CLKS_PER_BIT - 1);
localparam integer INDEX_BITS = $clog2(FRAME_BYTES + 1);
localparam logic [ADDR_BITS-1:0] PRE = ADDR_BITS'(PRETRIGGER);
localparam logic [ADDR_BITS:0] POST_SAMPLES = (ADDR_BITS + 1)'(DEPTH - PRETRIGGER);
localparam logic [15:0] DEPTH16 = 16'(DEPTH);
localparam logic [15:0] PRE16 = 16'(PRETRIGGER);
typedef enum logic [2:0] { IDLE, FILL, WAIT, POST, SEND } state_t;
state_t state;
logic [WIDTH-1:0] buffer [DEPTH];
logic [ADDR_BITS-1:0] write_addr, read_addr;
logic [ADDR_BITS:0] recorded;
logic [WIDTH-1:0] sample_q;
logic [8*SAMPLE_BYTES-1:0] sample_wide;
logic trigger_hit;
// Byte sequencer and its UART.
logic [INDEX_BITS-1:0] byte_index;
logic [$clog2(SAMPLE_BYTES + 1)-1:0] sample_byte;
logic [7:0] checksum, next_byte;
logic [9:0] shift;
logic [3:0] bits_left;
logic [COUNT_WIDTH-1:0] ticks;
logic uart_busy;
assign trigger_hit = ((probe ^ trigger_value) & trigger_mask) == '0;
assign sample_wide = (8 * SAMPLE_BYTES)'(sample_q);
assign armed = (state == FILL) || (state == WAIT) || (state == POST);
assign sending = (state == SEND);
// The byte to send at byte_index: header, then samples, then the checksum.
always_comb begin
case (byte_index)
INDEX_BITS'(0): next_byte = 8'hA5;
INDEX_BITS'(1): next_byte = 8'h5A;
INDEX_BITS'(2): next_byte = 8'(WIDTH);
INDEX_BITS'(3): next_byte = DEPTH16[7:0];
INDEX_BITS'(4): next_byte = DEPTH16[15:8];
INDEX_BITS'(5): next_byte = PRE16[7:0];
INDEX_BITS'(6): next_byte = PRE16[15:8];
default: next_byte = (byte_index == INDEX_BITS'(FRAME_BYTES - 1)) ? checksum : sample_wide[8*sample_byte +: 8];
endcase
end
// Synchronous read, so the buffer can map to block RAM.
always_ff @(posedge clk) begin
sample_q <= buffer[read_addr];
if (state == FILL || state == WAIT || state == POST) buffer[write_addr] <= probe;
end
always_ff @(posedge clk) begin
if (rst) begin
state <= IDLE;
write_addr <= '0;
read_addr <= '0;
recorded <= '0;
byte_index <= '0;
sample_byte <= '0;
checksum <= '0;
shift <= '1;
bits_left <= '0;
ticks <= '0;
uart_busy <= 1'b0;
tx <= 1'b1;
end else begin
case (state)
IDLE: if (arm) begin
write_addr <= '0;
recorded <= '0;
state <= (PRETRIGGER == 0) ? WAIT : FILL;
end
FILL: begin
write_addr <= write_addr + 1'b1;
if (recorded + 1'b1 == (ADDR_BITS + 1)'(PRETRIGGER)) state <= WAIT;
recorded <= recorded + 1'b1;
end
WAIT: begin
write_addr <= write_addr + 1'b1;
if (trigger_hit) begin
// This sample is the trigger; the window starts PRETRIGGER samples before it.
read_addr <= write_addr - PRE;
recorded <= (ADDR_BITS + 1)'(1);
if (POST_SAMPLES == (ADDR_BITS + 1)'(1)) begin
state <= SEND;
byte_index <= '0;
sample_byte <= '0;
checksum <= '0;
end else state <= POST;
end
end
POST: begin
write_addr <= write_addr + 1'b1;
recorded <= recorded + 1'b1;
if (recorded + 1'b1 == POST_SAMPLES) begin
state <= SEND;
byte_index <= '0;
sample_byte <= '0;
checksum <= '0;
end
end
SEND: begin
if (!uart_busy) begin
if (byte_index == INDEX_BITS'(FRAME_BYTES)) state <= IDLE;
else begin
// Start, 8 data bits, stop; shifted out least significant bit first.
shift <= {1'b1, next_byte, 1'b0};
bits_left <= 4'd10;
ticks <= '0;
uart_busy <= 1'b1;
checksum <= checksum ^ next_byte;
byte_index <= byte_index + 1'b1;
if (byte_index >= INDEX_BITS'(HEADER_BYTES) && byte_index < INDEX_BITS'(FRAME_BYTES - 1)) begin
if (sample_byte == ($clog2(SAMPLE_BYTES + 1))'(SAMPLE_BYTES - 1)) begin
sample_byte <= '0;
read_addr <= read_addr + 1'b1;
end else sample_byte <= sample_byte + 1'b1;
end
end
end
end
default: state <= IDLE;
endcase
// The UART: one bit every CLKS_PER_BIT clocks.
if (uart_busy) begin
tx <= shift[0];
if (ticks == LAST_TICK) begin
ticks <= '0;
shift <= {1'b1, shift[9:1]};
bits_left <= bits_left - 1'b1;
if (bits_left == 4'd1) uart_busy <= 1'b0;
end else ticks <= ticks + 1'b1;
end else tx <= 1'b1;
end
end
endmodule