RTL coding decisions can make ATPG dramatically easier or harder. A long idle timeout is hard to control: reaching it needs hundreds of quiet cycles per pattern.
The safe override shortens the wait in test mode only. It never touches enables that guard access, and it changes nothing when test mode is low.
Read the path left to right: only the limit input changes with test mode.
Without the override, every pattern that needs done_o must first spend 300 quiet cycles; with it, 3.
The counter, the compare and the done_o register are the same hardware in both modes, so test patterns exercise the real logic.
With test_mode_i low, the mux passes 300 and the block behaves exactly as before.
The mode input must be synchronous to clk. The threshold applies to the elapsed continuous idle interval: lowering it below the elapsed count completes on the next edge, while raising it before completion extends the wait. Once done, completion stays set until idle falls or reset asserts. Both limits must be positive.
`timescale 1ns/1ps
module idle_terminate #(
parameter FUNC_LIMIT = 300,
parameter TEST_LIMIT = 3
) (
input logic clk,
input logic rst_n,
input logic idle_i,
input logic test_mode_i,
output logic done_o
);
localparam int MAXL = (FUNC_LIMIT > TEST_LIMIT) ? FUNC_LIMIT : TEST_LIMIT;
logic [$clog2(MAXL+1)-1:0] idle_cnt, limit;
assign limit = test_mode_i ? TEST_LIMIT : FUNC_LIMIT;
initial begin
if (FUNC_LIMIT < 1 || TEST_LIMIT < 1)
$fatal(1, "idle_terminate: limits must be positive");
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
idle_cnt <= '0;
done_o <= 1'b0;
end else if (!idle_i) begin
idle_cnt <= '0;
done_o <= 1'b0;
end else if (done_o || idle_cnt >= limit - 1'b1) begin
done_o <= 1'b1;
end else begin
idle_cnt <= idle_cnt + 1'b1;
done_o <= 1'b0;
end
end
endmoduleA one-cycle error pulse is easy to miss between captures. A sticky flag latches the first pulse and holds it until reset, so any later shift-out sees it.
`timescale 1ns/1ps
module sticky_fault (
input logic clk,
input logic rst_n,
input logic error_pulse_i,
output logic fault_seen_o
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
fault_seen_o <= 1'b0;
else if (error_pulse_i)
fault_seen_o <= 1'b1;
end
endmoduleBoth idioms fail the same way: the override leaks into functional mode, or the flag clears before anyone reads it. The testbench checks both boundaries.
done_o?Low. The counter is far below the functional limit, and test mode contributes nothing while low.
High. The flag holds until reset, so the pulse survives to the next capture.
Both modules plus a testbench that proves the functional wait is untouched, the test wait is short, and the flag sticks.
`timescale 1ns/1ps
module idle_terminate #(
parameter FUNC_LIMIT = 300,
parameter TEST_LIMIT = 3
) (
input logic clk,
input logic rst_n,
input logic idle_i,
input logic test_mode_i,
output logic done_o
);
localparam int MAXL = (FUNC_LIMIT > TEST_LIMIT) ? FUNC_LIMIT : TEST_LIMIT;
logic [$clog2(MAXL+1)-1:0] idle_cnt, limit;
assign limit = test_mode_i ? TEST_LIMIT : FUNC_LIMIT;
initial begin
if (FUNC_LIMIT < 1 || TEST_LIMIT < 1)
$fatal(1, "idle_terminate: limits must be positive");
end
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
idle_cnt <= '0;
done_o <= 1'b0;
end else if (!idle_i) begin
idle_cnt <= '0;
done_o <= 1'b0;
end else if (done_o || idle_cnt >= limit - 1'b1) begin
done_o <= 1'b1;
end else begin
idle_cnt <= idle_cnt + 1'b1;
done_o <= 1'b0;
end
end
endmodule
module sticky_fault (
input logic clk,
input logic rst_n,
input logic error_pulse_i,
output logic fault_seen_o
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
fault_seen_o <= 1'b0;
else if (error_pulse_i)
fault_seen_o <= 1'b1;
end
endmodule