mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
fpga: Update SPI simulation testbenches
Extend the SPI slave model to propagate simulation failures to the test execution framework. Extract the timing monitor from the control-port SPI testbench so it can be shared by the GPIO SPI simulations while keeping timing checks centralized and consistent.
This commit is contained in:
parent
440ed5ae76
commit
772e682503
@ -42,6 +42,7 @@ module ctrlport_spi_adrv_tb #(
|
||||
import ctrlport_pkg::*;
|
||||
import ctrlport_spi_adrv_pkg::*;
|
||||
import ctrlport_bfm_pkg::*;
|
||||
import spi_slave_bfm_pkg::*;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
@ -83,11 +84,10 @@ module ctrlport_spi_adrv_tb #(
|
||||
|
||||
// Expected SPI half-period in simulation time. All SPI signal edges are
|
||||
// registered on posedge clk, so every half-period is exactly this long.
|
||||
realtime half_per_time = (HALF_PER + 1) * CLK_PERIOD_NS * 1.0ns;
|
||||
time half_per_time;
|
||||
|
||||
// Number of CS_N assertions observed by the timing monitor. Checked at the
|
||||
// end of the test to confirm the monitor actually ran for every transaction.
|
||||
int cs_n_monitor_count = 0; // Incremented by the timing monitor
|
||||
// Number of issued transactions. Checked against the timing monitor's
|
||||
// observed transaction count at the end of the test.
|
||||
int expected_cs_n_count = 0; // Incremented once per issued transaction
|
||||
|
||||
|
||||
@ -123,7 +123,16 @@ module ctrlport_spi_adrv_tb #(
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic spi_sclk, spi_cs_n, spi_mosi, spi_miso;
|
||||
// SPI interface
|
||||
logic spi_sclk, spi_cs_n, spi_mosi, spi_miso;
|
||||
spi_if spi_monitor_if ();
|
||||
|
||||
assign spi_monitor_if.cs_n = spi_cs_n;
|
||||
assign spi_monitor_if.sck = spi_sclk;
|
||||
assign spi_monitor_if.mosi = spi_mosi;
|
||||
assign spi_monitor_if.miso = spi_miso;
|
||||
|
||||
spi_timing_monitor_bfm spi_timing_mon;
|
||||
|
||||
logic [7:0] miso_byte_ctr = '0;
|
||||
logic [7:0] miso_shift = '0;
|
||||
@ -1811,6 +1820,12 @@ module ctrlport_spi_adrv_tb #(
|
||||
$sformatf("REG_HALF_PER after write: got 0x%h, expected 0x%h",
|
||||
rd_val[HALF_PER_W-1:0], NEW_HALF_PER));
|
||||
half_per_time = (NEW_HALF_PER + 1) * CLK_PERIOD_NS * 1.0ns;
|
||||
spi_timing_mon.set_timing(
|
||||
.half_per_time (half_per_time),
|
||||
.cs_hold_time (CS_HOLD * half_per_time),
|
||||
.cs_to_first_rise_time (half_per_time),
|
||||
.cs_guard_time (CS_GUARD * half_per_time)
|
||||
);
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Write 3 bytes at the new HALF_PER setting
|
||||
@ -1827,156 +1842,17 @@ module ctrlport_spi_adrv_tb #(
|
||||
$sformatf("REG_HALF_PER after restore: got 0x%h, expected 0x%h",
|
||||
rd_val[HALF_PER_W-1:0], HALF_PER));
|
||||
half_per_time = (HALF_PER + 1) * CLK_PERIOD_NS * 1.0ns;
|
||||
spi_timing_mon.set_timing(
|
||||
.half_per_time (half_per_time),
|
||||
.cs_hold_time (CS_HOLD * half_per_time),
|
||||
.cs_to_first_rise_time (half_per_time),
|
||||
.cs_guard_time (CS_GUARD * half_per_time)
|
||||
);
|
||||
test.end_test();
|
||||
|
||||
endtask : test_sclk_freq
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// SPI Timing Monitor
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Runs in parallel with the main test. On every transaction (CS_N
|
||||
// assertion), checks:
|
||||
//
|
||||
// 1. CS_N assert -> first SCLK rise is exactly 1 half-period
|
||||
// 2. SCLK high period (posedge -> negedge) is exactly 1 half-period
|
||||
// 3. SCLK low period (negedge -> posedge):
|
||||
// - intra-byte (fall N where N % 8 != 0): exactly 1 half-period
|
||||
// - inter-byte (fall N where N % 8 == 0): >= 1 half-period
|
||||
// 4. Last SCLK fall -> CS_N deassert:
|
||||
// - CS_HOLD=1 : exactly 1 half-period
|
||||
// - CS_HOLD=0 : 0 (same registered clock edge)
|
||||
// 5. CS_N deassert -> next CS_N assert : >= CS_GUARD half-periods
|
||||
//
|
||||
// CS_HOLD and CS_GUARD are read hierarchically from dut.axis_spi_i.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
initial begin : spi_timing_monitor
|
||||
realtime t_cs_assert, t_rise, t_fall, t_cs_deassert, t_cs_deassert_prev;
|
||||
int rise_cnt; // rising edge count within the current transaction
|
||||
static bit first_txn = 1'b1; // suppress guard check on first transaction
|
||||
|
||||
@(negedge rst); // wait for reset to deassert
|
||||
|
||||
forever begin
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Wait for CS_N assertion (start of transaction)
|
||||
//-----------------------------------------------------------------------
|
||||
@(negedge spi_cs_n);
|
||||
t_cs_assert = $realtime;
|
||||
cs_n_monitor_count++;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Check 5: CS_N guard: previous transaction's CS_N deassert must be >=
|
||||
// CS_GUARD half-periods before this assertion. Checked here (at the
|
||||
// start of each transaction) so the guard @(negedge spi_cs_n) doesn't
|
||||
// consume a transaction count. Skipped on the first transaction.
|
||||
//-----------------------------------------------------------------------
|
||||
if (!first_txn) begin
|
||||
`ASSERT_ERROR(t_cs_assert - t_cs_deassert_prev >=
|
||||
dut.axis_spi_i.CS_GUARD * half_per_time,
|
||||
$sformatf("CS_N guard too short: minimum %0t, got %0t",
|
||||
dut.axis_spi_i.CS_GUARD * half_per_time,
|
||||
t_cs_assert - t_cs_deassert_prev));
|
||||
end
|
||||
first_txn = 1'b0;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Check 1: CS_N assert -> first SCLK rising edge = exactly 1 half-period
|
||||
//-----------------------------------------------------------------------
|
||||
@(posedge spi_sclk);
|
||||
t_rise = $realtime;
|
||||
rise_cnt = 1;
|
||||
`ASSERT_ERROR(t_rise - t_cs_assert == half_per_time,
|
||||
$sformatf("CS_N assert to first SCLK rise: expected %0t, got %0t",
|
||||
half_per_time, t_rise - t_cs_assert));
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Checks 2, 3, 4: Walk all SCLK edges until CS_N deasserts.
|
||||
//
|
||||
// State at loop top: we are at a posedge sclk; rise_cnt is current.
|
||||
//
|
||||
// Step A: Wait for negedge sclk OR posedge cs_n (CS_HOLD=0 causes
|
||||
// both to arrive on the same clock edge).
|
||||
// Step B: If cs_n is still low, wait for posedge sclk OR posedge
|
||||
// cs_n (CS_HOLD=1 deasserts cs_n here after ST_HOLD).
|
||||
//-----------------------------------------------------------------------
|
||||
forever begin
|
||||
|
||||
// Step A: wait for SCLK fall (or simultaneous CS_N deassert)
|
||||
fork : wait_fall
|
||||
begin : fall_th @(negedge spi_sclk); end
|
||||
begin : cs0_th @(posedge spi_cs_n); end
|
||||
join_any
|
||||
disable wait_fall;
|
||||
t_fall = $realtime;
|
||||
|
||||
// Check 2: SCLK high period must be exactly 1 half-period
|
||||
`ASSERT_ERROR(t_fall - t_rise == half_per_time,
|
||||
$sformatf("SCLK high period: expected %0t, got %0t",
|
||||
half_per_time, t_fall - t_rise));
|
||||
|
||||
if (spi_cs_n) begin
|
||||
// CS_HOLD=0: CS_N deasserted on the same clock edge as last SCLK
|
||||
// fall, so time difference is 0.
|
||||
t_cs_deassert = t_fall;
|
||||
`ASSERT_ERROR(dut.axis_spi_i.CS_HOLD == 0,
|
||||
"CS_N deasserted coincident with last SCLK fall but CS_HOLD != 0");
|
||||
break;
|
||||
end
|
||||
|
||||
// Step B: wait for SCLK rise or CS_N deassert (CS_HOLD=1)
|
||||
fork : wait_rise
|
||||
begin : rise_th @(posedge spi_sclk); end
|
||||
begin : cs1_th @(posedge spi_cs_n); end
|
||||
join_any
|
||||
disable wait_rise;
|
||||
t_rise = $realtime;
|
||||
rise_cnt++;
|
||||
|
||||
// Check 3: SCLK low period.
|
||||
//
|
||||
// rise_cnt just incremented, so the preceding fall was fall number
|
||||
// (rise_cnt - 1). A fall at a multiple of 8 is an inter-byte boundary
|
||||
// where the FSM waits in ST_IDLE for the next byte; duration is
|
||||
// unbounded but must be >= 1 half-period. All other falls are
|
||||
// intra-byte and must be exactly 1 half-period.
|
||||
if ((rise_cnt - 1) % 8 == 0) begin
|
||||
`ASSERT_ERROR(t_rise - t_fall >= half_per_time,
|
||||
$sformatf("SCLK low period (inter-byte) too short: minimum %0t, got %0t",
|
||||
half_per_time, t_rise - t_fall));
|
||||
end else begin
|
||||
`ASSERT_ERROR(t_rise - t_fall == half_per_time,
|
||||
$sformatf("SCLK low period (intra-byte, fall %0d): expected %0t, got %0t",
|
||||
rise_cnt - 1, half_per_time, t_rise - t_fall));
|
||||
end
|
||||
|
||||
if (spi_cs_n) begin
|
||||
// CS_HOLD=1: CS_N deasserted exactly 1 half-period after last
|
||||
// SCLK fall (after ST_HOLD).
|
||||
t_cs_deassert = t_rise;
|
||||
`ASSERT_ERROR(dut.axis_spi_i.CS_HOLD == 1,
|
||||
"CS_N deasserted 1 half-period after SCLK fall but CS_HOLD != 1");
|
||||
`ASSERT_ERROR(t_cs_deassert - t_fall == half_per_time,
|
||||
$sformatf("CS_HOLD=1 hold time: expected %0t, got %0t",
|
||||
half_per_time, t_cs_deassert - t_fall));
|
||||
break;
|
||||
end
|
||||
// cs_n still low and we got posedge sclk; continue to next bit
|
||||
|
||||
end // inner forever (SCLK edge walk)
|
||||
|
||||
// Save deassert time for next iteration's guard check
|
||||
t_cs_deassert_prev = t_cs_deassert;
|
||||
|
||||
end // outer forever (transaction loop)
|
||||
|
||||
end : spi_timing_monitor
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Main test
|
||||
//---------------------------------------------------------------------------
|
||||
@ -2005,6 +1881,17 @@ module ctrlport_spi_adrv_tb #(
|
||||
clk_gen.reset();
|
||||
@(negedge rst);
|
||||
|
||||
// Create and start passive SPI timing checks after reset is released.
|
||||
half_per_time = (HALF_PER + 1) * CLK_PERIOD_NS * 1.0ns;
|
||||
spi_timing_mon = new(
|
||||
.iface (spi_monitor_if),
|
||||
.half_per_time (half_per_time),
|
||||
.cs_hold_time (CS_HOLD * half_per_time),
|
||||
.cs_to_first_rise_time (half_per_time),
|
||||
.cs_guard_time (CS_GUARD * half_per_time)
|
||||
);
|
||||
spi_timing_mon.run();
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Tests
|
||||
//-------------------------------------------------------------------------
|
||||
@ -2025,9 +1912,15 @@ module ctrlport_spi_adrv_tb #(
|
||||
// Confirm the timing monitor ran exactly once per SPI transaction.
|
||||
// expected_cs_n_count is incremented once per issued transaction, so it
|
||||
// tracks the true number of transactions without manual bookkeeping.
|
||||
`ASSERT_ERROR(cs_n_monitor_count == expected_cs_n_count,
|
||||
`ASSERT_ERROR(spi_timing_mon.get_transaction_count() == expected_cs_n_count,
|
||||
$sformatf("Timing monitor CS_N count mismatch: expected %0d, got %0d",
|
||||
expected_cs_n_count, cs_n_monitor_count));
|
||||
expected_cs_n_count, spi_timing_mon.get_transaction_count()));
|
||||
`ASSERT_ERROR(spi_timing_mon.get_error_count() == 0,
|
||||
$sformatf("SPI timing monitor reported %0d errors",
|
||||
spi_timing_mon.get_error_count()));
|
||||
|
||||
// Stop the monitor before the next parameterized testbench instance runs.
|
||||
disable fork;
|
||||
|
||||
// Don't call $finish; other instances of this testbench may follow.
|
||||
test.end_tb(0);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, a National Instruments Brand
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
package spi_slave_bfm_pkg;
|
||||
|
||||
timeunit 1ns / 1ps;
|
||||
|
||||
class spi_slave_bfm #(int TRANSACTION_LENGTH = 32);
|
||||
|
||||
local mailbox #(logic [TRANSACTION_LENGTH-1:0]) mosi_mailbox;
|
||||
@ -24,6 +26,9 @@ package spi_slave_bfm_pkg;
|
||||
// link to interface
|
||||
local virtual spi_if iface;
|
||||
|
||||
|
||||
local int unsigned error_count;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ------------------------------------------------------------------------
|
||||
@ -33,6 +38,12 @@ package spi_slave_bfm_pkg;
|
||||
this.iface = iface;
|
||||
mosi_mailbox = new;
|
||||
miso_mailbox = new;
|
||||
error_count = 0;
|
||||
endfunction
|
||||
|
||||
// Number of errors reported by the BFM.
|
||||
function int unsigned get_error_count();
|
||||
return error_count;
|
||||
endfunction
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
@ -81,6 +92,11 @@ package spi_slave_bfm_pkg;
|
||||
join_none
|
||||
endtask
|
||||
|
||||
local function void report_error(string msg);
|
||||
error_count++;
|
||||
$error("%s @ t=%0t", msg, $time);
|
||||
endfunction
|
||||
|
||||
// check constantly the status of the CS and SCK signal
|
||||
task cs_sck_check_body();
|
||||
automatic int clock_edge_counter = 0;
|
||||
@ -99,7 +115,8 @@ package spi_slave_bfm_pkg;
|
||||
// check if the transaction length is correct
|
||||
// 2 clock edges per transaction bit
|
||||
assert(clock_edge_counter <= 2*TRANSACTION_LENGTH) else
|
||||
$error("Too many SCLK cycles received. Clock cycles: %0d", clock_edge_counter);
|
||||
report_error($sformatf(
|
||||
"Too many SCLK cycles received. Clock cycles: %0d", clock_edge_counter));
|
||||
end
|
||||
end
|
||||
// ignore special case when cs turn to high impedance
|
||||
@ -107,7 +124,8 @@ package spi_slave_bfm_pkg;
|
||||
// if cs goes high check if the transaction length is correct
|
||||
// 2 clock edges per transaction bit
|
||||
assert(clock_edge_counter == 2*TRANSACTION_LENGTH) else
|
||||
$error("Unexpected SPI CS interrupt. Clock cycles: %0d", clock_edge_counter);
|
||||
report_error($sformatf(
|
||||
"Unexpected SPI CS interrupt. Clock cycles: %0d", clock_edge_counter));
|
||||
end
|
||||
end
|
||||
endtask
|
||||
@ -135,12 +153,13 @@ package spi_slave_bfm_pkg;
|
||||
end
|
||||
// compare received data with expected data
|
||||
assert(received_data == expected_data) else
|
||||
$error("Unexpected data received on SPI interface. Expected: %h Received: %h",
|
||||
expected_data, received_data);
|
||||
report_error($sformatf(
|
||||
"Unexpected data received on SPI interface. Expected: %h Received: %h",
|
||||
expected_data, received_data));
|
||||
end else begin
|
||||
// ignore special case when cs turn to high impedance
|
||||
if (iface.cs_n !== 'z) begin
|
||||
$error("No data available in SPI BFM MOSI mailbox.");
|
||||
report_error("No data available in SPI BFM MOSI mailbox.");
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -174,7 +193,7 @@ package spi_slave_bfm_pkg;
|
||||
end else begin
|
||||
// ignore special case when cs turn to high impedance
|
||||
if (iface.cs_n !== 'z) begin
|
||||
$error("No data available in SPI BFM MISO mailbox.");
|
||||
report_error("No data available in SPI BFM MISO mailbox.");
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -182,4 +201,253 @@ package spi_slave_bfm_pkg;
|
||||
|
||||
endclass
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// SPI timing monitor BFM
|
||||
// ------------------------------------------------------------------------
|
||||
// Passive monitor for CPOL=0, CPHA=0 SPI timing checks.
|
||||
//
|
||||
// On each transaction (CS_N low pulse), the monitor checks:
|
||||
// 1) CS_N assert to first SCLK rise (minimum)
|
||||
// 2) SCLK high period (exact)
|
||||
// 3) SCLK low period:
|
||||
// - intra-byte: exact
|
||||
// - inter-byte: minimum (optional)
|
||||
// 4) CS hold time after final SCLK fall (minimum)
|
||||
// 5) CS guard time between transactions (minimum)
|
||||
//
|
||||
// The monitor is reusable across tests within a testbench by updating timing with
|
||||
// set_timing() when expected timings change.
|
||||
class spi_timing_monitor_bfm;
|
||||
|
||||
local virtual spi_if iface;
|
||||
|
||||
local time half_per_time;
|
||||
local time cs_hold_time;
|
||||
local time cs_to_first_rise_time;
|
||||
local time cs_guard_time;
|
||||
|
||||
local int unsigned bits_per_byte;
|
||||
local bit check_inter_byte_gaps;
|
||||
|
||||
local int unsigned transaction_count;
|
||||
local int unsigned error_count;
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// Constructor
|
||||
// ----------------------------------------------------------------------
|
||||
// Arguments:
|
||||
// iface: SPI interface to monitor
|
||||
// half_per_time: expected SCLK half period (must be non-zero)
|
||||
// cs_hold_time: minimum time from last SCLK fall to CS_N deassert
|
||||
// (0 disables CS hold checks)
|
||||
// cs_to_first_rise_time: minimum time from CS_N assert to first SCLK rise
|
||||
// (0 disables first-rise check)
|
||||
// cs_guard_time: minimum time between transactions
|
||||
// bits_per_byte: bit grouping for inter-byte low-gap checks
|
||||
// check_inter_byte_gaps: enable minimum-gap checks at byte boundaries
|
||||
function new(
|
||||
virtual spi_if iface,
|
||||
time half_per_time,
|
||||
time cs_hold_time = 0,
|
||||
time cs_to_first_rise_time = 0,
|
||||
time cs_guard_time = 0,
|
||||
int unsigned bits_per_byte = 8,
|
||||
bit check_inter_byte_gaps = 1'b1
|
||||
);
|
||||
assert (half_per_time != 0)
|
||||
else $fatal(1, "half_per_time must be non-zero");
|
||||
|
||||
this.iface = iface;
|
||||
this.half_per_time = half_per_time;
|
||||
this.cs_hold_time = cs_hold_time;
|
||||
this.cs_to_first_rise_time = cs_to_first_rise_time;
|
||||
this.cs_guard_time = cs_guard_time;
|
||||
this.bits_per_byte = bits_per_byte;
|
||||
this.check_inter_byte_gaps = check_inter_byte_gaps;
|
||||
this.transaction_count = 0;
|
||||
this.error_count = 0;
|
||||
endfunction
|
||||
|
||||
// Update timing expectations at runtime.
|
||||
// Use this after changing SPI rate configuration in the DUT.
|
||||
function void set_timing(
|
||||
time half_per_time,
|
||||
time cs_hold_time,
|
||||
time cs_to_first_rise_time,
|
||||
time cs_guard_time
|
||||
);
|
||||
assert (half_per_time != 0)
|
||||
else $fatal(1, "half_per_time must be non-zero");
|
||||
|
||||
this.half_per_time = half_per_time;
|
||||
this.cs_hold_time = cs_hold_time;
|
||||
this.cs_to_first_rise_time = cs_to_first_rise_time;
|
||||
this.cs_guard_time = cs_guard_time;
|
||||
endfunction
|
||||
|
||||
// Number of observed CS_N assertions (transactions).
|
||||
function int unsigned get_transaction_count();
|
||||
return transaction_count;
|
||||
endfunction
|
||||
|
||||
// Number of timing violations reported by the monitor.
|
||||
function int unsigned get_error_count();
|
||||
return error_count;
|
||||
endfunction
|
||||
|
||||
// Start monitoring. Intended to run in a forked process.
|
||||
task run();
|
||||
fork
|
||||
monitor_body();
|
||||
join_none;
|
||||
endtask
|
||||
|
||||
local function void report_error(string msg);
|
||||
error_count++;
|
||||
$error("%s @ t=%0t", msg, $time);
|
||||
endfunction
|
||||
|
||||
// Main timing monitor loop.
|
||||
// This runs forever and validates each SPI transaction independently.
|
||||
local task monitor_body();
|
||||
time t_cs_assert;
|
||||
time t_rise;
|
||||
time t_fall;
|
||||
time t_cs_deassert;
|
||||
time t_cs_deassert_prev;
|
||||
int rise_cnt;
|
||||
bit first_txn;
|
||||
|
||||
first_txn = 1'b1;
|
||||
|
||||
forever begin
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Wait for CS_N assertion (start of transaction)
|
||||
//-----------------------------------------------------------------------
|
||||
@(negedge iface.cs_n);
|
||||
t_cs_assert = $time;
|
||||
transaction_count++;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Check 5: CS_N guard: previous transaction's CS_N deassert must be >=
|
||||
// CS_GUARD_TIME before this assertion. Checked here (at the
|
||||
// start of each transaction) so the guard @(negedge iface.cs_n) doesn't
|
||||
// consume a transaction count. Skipped on the first transaction.
|
||||
//-----------------------------------------------------------------------
|
||||
if (!first_txn) begin
|
||||
if (t_cs_assert - t_cs_deassert_prev < cs_guard_time) begin
|
||||
report_error($sformatf(
|
||||
"CS_N guard too short: minimum %0t, got %0t",
|
||||
cs_guard_time,
|
||||
t_cs_assert - t_cs_deassert_prev));
|
||||
end
|
||||
end
|
||||
first_txn = 1'b0;
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Check 1: CS_N assert -> first SCLK rising edge >= cs_to_first_rise_time
|
||||
//-----------------------------------------------------------------------
|
||||
@(posedge iface.sck);
|
||||
t_rise = $time;
|
||||
rise_cnt = 1;
|
||||
if ((cs_to_first_rise_time != 0) && (t_rise - t_cs_assert < cs_to_first_rise_time)) begin
|
||||
report_error($sformatf(
|
||||
"CS_N assert to first SCLK rise too short: minimum %0t, got %0t",
|
||||
cs_to_first_rise_time, t_rise - t_cs_assert));
|
||||
end
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Checks 2, 3, 4: Walk all SCLK edges until CS_N deasserts.
|
||||
//
|
||||
// State at loop top: we are at a posedge sclk; rise_cnt is current.
|
||||
//
|
||||
// Step A: Wait for negedge sclk OR posedge cs_n.
|
||||
// Step B: If cs_n is still low, wait for posedge sclk OR posedge
|
||||
// cs_n.
|
||||
//-----------------------------------------------------------------------
|
||||
forever begin
|
||||
|
||||
// Step A: wait for SCLK fall (or simultaneous CS_N deassert)
|
||||
fork : wait_fall
|
||||
begin : fall_th @(negedge iface.sck); end
|
||||
begin : cs0_th @(posedge iface.cs_n); end
|
||||
join_any
|
||||
disable wait_fall;
|
||||
t_fall = $time;
|
||||
|
||||
// Check 2: SCLK high period must be exactly half_per_time
|
||||
// This indirectly also ensures that CS is not deasserted too early
|
||||
if (t_fall - t_rise != half_per_time) begin
|
||||
report_error($sformatf(
|
||||
"SCLK high period: expected %0t, got %0t",
|
||||
half_per_time, t_fall - t_rise));
|
||||
end
|
||||
|
||||
if (iface.cs_n) begin
|
||||
// CS_N deasserted on the same clock edge as last SCLK
|
||||
t_cs_deassert = t_fall;
|
||||
if ((cs_hold_time != 0)) begin
|
||||
report_error($sformatf(
|
||||
"CS hold time at SCLK fall too short: minimum %0t, got 0",
|
||||
cs_hold_time));
|
||||
end
|
||||
break;
|
||||
end
|
||||
|
||||
// Step B: wait for SCLK rise or CS_N deassert (CS_HOLD=1)
|
||||
fork : wait_rise
|
||||
begin : rise_th @(posedge iface.sck); end
|
||||
begin : cs1_th @(posedge iface.cs_n); end
|
||||
join_any
|
||||
disable wait_rise;
|
||||
|
||||
if (iface.cs_n) begin
|
||||
// CS_N deasserted after the last SCLK fall; enforce minimum hold.
|
||||
t_cs_deassert = $time;
|
||||
if ((cs_hold_time != 0) && (t_cs_deassert - t_fall < cs_hold_time)) begin
|
||||
report_error($sformatf(
|
||||
"CS hold time after last SCLK fall too short: minimum %0t, got %0t",
|
||||
cs_hold_time, t_cs_deassert - t_fall));
|
||||
end
|
||||
break;
|
||||
end
|
||||
|
||||
t_rise = $time;
|
||||
rise_cnt++;
|
||||
|
||||
// Check 3: SCLK low period.
|
||||
//
|
||||
// rise_cnt just incremented, so the preceding fall was fall number
|
||||
// (rise_cnt - 1). A fall at a multiple of bits_per_byte is an
|
||||
// inter-byte boundary
|
||||
// where the FSM waits in ST_IDLE for the next byte; duration is
|
||||
// unbounded but must be >= 1 half-period. All other falls are
|
||||
// intra-byte and must be exactly 1 half-period.
|
||||
if (check_inter_byte_gaps && ((rise_cnt - 1) % bits_per_byte == 0)) begin
|
||||
if (t_rise - t_fall < half_per_time) begin
|
||||
report_error($sformatf(
|
||||
"SCLK low period (inter-byte) too short: minimum %0t, got %0t",
|
||||
half_per_time, t_rise - t_fall));
|
||||
end
|
||||
end else begin
|
||||
if (t_rise - t_fall != half_per_time) begin
|
||||
report_error($sformatf(
|
||||
"SCLK low period: expected %0t, got %0t",
|
||||
half_per_time, t_rise - t_fall));
|
||||
end
|
||||
end
|
||||
|
||||
// CS_N is still low and we got posedge sclk; continue to next bit
|
||||
|
||||
end // inner forever (SCLK edge walk)
|
||||
|
||||
// Save deassert time for next iteration's guard check
|
||||
t_cs_deassert_prev = t_cs_deassert;
|
||||
end // outer forever (transaction loop)
|
||||
endtask
|
||||
|
||||
endclass
|
||||
|
||||
endpackage
|
||||
|
||||
Loading…
Reference in New Issue
Block a user