Timeout is such an important pattern that cocotb provide with_timeout for coroutines to call other routines with timeouts and return SimTimeoutError if coroutine times out.

Wait on any awaitable, throw an exception if it waits longer than the given time.

When a coroutine is passed, the callee coroutine is started, the caller blocks until the callee completes, and the callee’s result is returned to the caller. If timeout occurs, the callee is killed and SimTimeoutError is raised.

await with_timeout(coro, 100, "ns")

I thought it would be good to create with_cycles_timeout to do something but using number of clock cycles (posedge).

import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, ClockCycles, First
from cocotb.result import SimTimeoutError

async def with_cycle_timeout(coro, clock, cycles):
    func_task       = cocotb.start_soon(coro)
    timeout_trigger = ClockCycles(clock, cycles)
    results         = await First(func_task, timeout_trigger)

    if results is timeout_trigger:
        raise SimTimeoutError(f"Timeout after {cycles} cycles")
    else:
        return results

async def dummy_write(dut, cycles):
    for i in range(cycles):
        await RisingEdge(dut.clk)
        cocotb.log.info(f"cycle {i+1}/{cycles}")

    cocotb.log.info(f"Finished after {cycles} cycles")
    return 0xDEADBEEF

@cocotb.test()
async def test_timeout(dut):
    cocotb.start_soon(Clock(dut.clk, 10, unit='ns').start())

    try:
        value = await with_cycle_timeout(dummy_write(dut, cycles=10),clock=dut.clk,cycles=20,)
        cocotb.log.info(f"Result: {value:#010x}")

        value = await with_cycle_timeout(dummy_write(dut, cycles=10),clock=dut.clk,cycles=3,)
        cocotb.log.info(f"Result: {value:#010x}")
    except SimTimeoutError as e:
        cocotb.log.error(f"Timeout: {e}")

in the first case, the routine returns the value but in the second case, it raise SimTimeoutError which is caught by the caller.

     0.00ns INFO     test                               cycle 1/10
    10.00ns INFO     test                               cycle 2/10
    20.00ns INFO     test                               cycle 3/10
    30.00ns INFO     test                               cycle 4/10
    40.00ns INFO     test                               cycle 5/10
    50.00ns INFO     test                               cycle 6/10
    60.00ns INFO     test                               cycle 7/10
    70.00ns INFO     test                               cycle 8/10
    80.00ns INFO     test                               cycle 9/10
    90.00ns INFO     test                               cycle 10/10
    90.00ns INFO     test                               Finished after 10 cycles
    90.00ns INFO     test                               Result: 0xdeadbeef
   100.00ns INFO     test                               cycle 1/10
   110.00ns INFO     test                               cycle 2/10
   120.00ns INFO     test                               cycle 3/10
   120.00ns ERROR    test                               Timeout: Timeout  after 3 cycles