Skip to content

Commit

Permalink
Improve force/release tests (cocotb#3828)
Browse files Browse the repository at this point in the history
  • Loading branch information
ktbarrett authored Apr 9, 2024
1 parent e7e4d43 commit 9c97de5
Show file tree
Hide file tree
Showing 2 changed files with 176 additions and 24 deletions.
19 changes: 5 additions & 14 deletions tests/designs/sample_module/sample_module.vhdl
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ entity sample_module is
stream_in_data_wide : in std_ulogic_vector(63 downto 0);
stream_in_data_dqword : in std_ulogic_vector(127 downto 0);
stream_in_valid : in std_ulogic;
stream_in_func_en : in std_ulogic;
stream_in_ready : out std_ulogic;
stream_in_real : in real;
stream_in_int : in integer;
Expand Down Expand Up @@ -85,18 +84,6 @@ architecture impl of sample_module is

type lutType is array (0 to 3, 0 to 6) of signed(10 downto 0);

function afunc(value : std_ulogic_vector) return std_ulogic_vector is
variable i: integer;
variable rv: std_ulogic_vector(7 downto 0);
begin
i := 0;
while i <= 7 loop
rv(i) := value(7-i);
i := i + 1;
end loop;
return rv;
end afunc;

signal cosLut0, sinLut0 : lutType;
signal cosLut1, sinLut1 : lutType;
signal cosLut, sinLut : lutType;
Expand Down Expand Up @@ -154,7 +141,11 @@ begin
stream_in_string_asciival_sum <= v_stream_in_string_asciival_sum;
end process;

stream_out_data_comb <= afunc(stream_in_data) when stream_in_func_en = '0' else stream_in_data;
process (stream_in_data) is
begin
stream_out_data_comb <= stream_in_data;
end process;

stream_in_ready <= stream_out_ready;
stream_out_real <= stream_in_real;
stream_out_int <= stream_in_int;
Expand Down
181 changes: 171 additions & 10 deletions tests/test_cases/test_force_release/test_force_release.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,181 @@
# Copyright cocotb contributors
# Licensed under the Revised BSD License, see LICENSE for details.
# SPDX-License-Identifier: BSD-3-Clause

"""Tests the Force/Freeze/Release features."""

import os

import cocotb
from cocotb.clock import Clock
from cocotb.handle import Force, Release
from cocotb.triggers import Timer
from cocotb.triggers import ClockCycles, Timer

SIM_NAME = cocotb.SIM_NAME.lower()


# Release doesn't work on GHDL (gh-3830)
# Force/Release doesn't work on Verilator (gh-3831)
@cocotb.test(expect_fail=SIM_NAME.startswith(("ghdl", "verilator")))
async def test_hdl_writes_dont_overwrite_force_combo(dut):
"""Test Forcing then later Releasing a combo signal."""

@cocotb.test(expect_fail=cocotb.SIM_NAME.lower().startswith(("ghdl", "verilator")))
async def test_force_release(dut):
"""
Test force and release on simulation handles
"""
await Timer(10, "ns")
dut.stream_in_data.value = 4

# Force the driven signal.
dut.stream_out_data_comb.value = Force(5)
await Timer(10, "ns")
assert dut.stream_in_data.value != dut.stream_out_data_comb.value
assert dut.stream_out_data_comb.value == 5

dut.stream_out_data_comb.value = Release()
# Release the driven signal.
# The driver signal is set again to trigger the process which recomputes the driven signal.
# This is done because releasing the driven signal does not cause the process to run again.
dut.stream_in_data.value = 3
dut.stream_out_data_comb.value = Release()
await Timer(10, "ns")
assert dut.stream_out_data_comb.value == 3


# Release doesn't work on GHDL (gh-3830)
# Force/Release doesn't work on Verilator (gh-3831)
@cocotb.test(expect_fail=SIM_NAME.startswith(("ghdl", "verilator")))
async def test_hdl_writes_dont_overwrite_force_registered(dut):
"""Test Forcing then Releasing a registered output."""
cocotb.start_soon(Clock(dut.clk, 10, "ns").start())
dut.stream_in_data.value = 4

# Force the driven signal.
dut.stream_out_data_registered.value = Force(5)
await ClockCycles(dut.clk, 2)
assert dut.stream_out_data_registered.value == 5

# Release the driven signal.
dut.stream_out_data_registered.value = Release()
await ClockCycles(dut.clk, 2)
assert dut.stream_out_data_registered.value == 4


# Release doesn't work on GHDL (gh-3830)
# Force/Release doesn't work on Verilator (gh-3831)
@cocotb.test(expect_fail=SIM_NAME.startswith("ghdl"))
async def test_force_followed_by_release_combo(dut):
"""Test if Force followed immediately by Release works on combo signals."""

dut.stream_in_data.value = 14

# Force driven signal then immediately release it.
dut.stream_out_data_comb.value = Force(23)
dut.stream_out_data_comb.value = Release()

# Check if the driven signal is actually released.
# The driver signal is set again to trigger the process which recomputes the driven signal.
# This is done because releasing the driven signal does not cause the process to run again.
dut.stream_in_data.value = 16
await Timer(10, "ns")
assert dut.stream_out_data_comb.value == 16


# Release doesn't work on GHDL (gh-3830)
# Force/Release doesn't work on Verilator (gh-3831)
@cocotb.test(expect_fail=SIM_NAME.startswith("ghdl"))
async def test_force_followed_by_release_registered(dut):
"""Test if Force followed immediately by Release works on registered signals."""

cocotb.start_soon(Clock(dut.clk, 10, "ns").start())
dut.stream_in_data.value = 90

# Force driven signal then immediately release it.
dut.stream_out_data_registered.value = Force(5)
dut.stream_out_data_registered.value = Release()

# Check if the driven signal is actually released.
await ClockCycles(dut.clk, 2)
assert dut.stream_out_data_registered.value == 90


questa_fli = (
SIM_NAME.startswith("modelsim") and os.getenv("VHDL_GPI_INTERFACE", "") == "fli"
)


riviera_vpi = (
SIM_NAME.startswith("riviera")
and os.getenv("TOPLEVEL_LANG", "verilog") == "verilog"
)


# Release doesn't work on GHDL (gh-3830)
# Force/Release doesn't work on Verilator (gh-3831)
# Riviera's VPI implicitly releases signal when overwriting forced signal with normal deposit (gh-3832)
# Questa's FLI allows overwriting forced signal with normal deposit (gh-3833)
@cocotb.test(
expect_fail=SIM_NAME.startswith(("ghdl", "verilator")) or riviera_vpi or questa_fli
)
async def test_cocotb_writes_dont_overwrite_force_combo(dut):
"""Test Deposits following a Force don't overwrite the value on combo signals."""
dut.stream_in_data.value = 56

# Force the driven signal.
dut.stream_out_data_comb.value = Force(10)
await Timer(10, "ns")
assert dut.stream_out_data_comb.value == 10

# Attempt depositing on the forced signal. This shouldn't change the value.
dut.stream_out_data_comb.value = 11
dut.stream_in_data.value = 70 # attempt to trigger a change in value
await Timer(10, "ns")
assert dut.stream_out_data_comb.value == 10

# Release the forced signal. The value should follow driver.
# The driver signal is set again to trigger the process which recomputes the driven signal.
# This is done because releasing the driven signal does not cause the process to run again.
dut.stream_in_data.value = 46
dut.stream_out_data_registered.value = Release()
await Timer(10, "ns")
assert dut.stream_in_data.value == 46


# Release doesn't work on GHDL (gh-3830)
# Force/Release doesn't work on Verilator (gh-3831)
# Riviera's VPI implicitly releases signal when overwriting forced signal with normal deposit (gh-3832)
# Questa's FLI allows overwriting forced signal with normal deposit (gh-3833)
@cocotb.test(
expect_fail=SIM_NAME.startswith(("ghdl", "verilator")) or questa_fli or riviera_vpi
)
async def test_cocotb_writes_dont_overwrite_force_registered(dut):
"""Test Deposits following a Force don't overwrite the value on registered signals."""
cocotb.start_soon(Clock(dut.clk, 10, "ns").start())
dut.stream_in_data.value = 77

# Force the driven signal.
dut.stream_out_data_registered.value = Force(10)
await ClockCycles(dut.clk, 2)
assert dut.stream_out_data_registered.value == 10

# Attempt depositing on the forced signal. This shouldn't change the value.
dut.stream_out_data_registered.value = 11
await ClockCycles(dut.clk, 2)
assert dut.stream_out_data_registered.value == 10

# Release the forced signal. The value should follow driver.
dut.stream_out_data_registered.value = Release()
await ClockCycles(dut.clk, 2)
assert dut.stream_in_data.value == 77


# Release and Freeze read current simulator values, not scheduled values (gh-3829)
@cocotb.test(expect_fail=True)
async def test_force_followed_by_release_correct_value(dut):
"""Test if Forcing then immediately Releasing a signal yields the correct value.
Due to the way that Release and Freeze are implemented (reading the current value then doing a set with that value) this test will always fail.
Leaving this test for when a better implementation of Freeze and Release are implemented.
"""
dut.stream_in_data.value = 19
await Timer(10, "ns")
assert dut.stream_in_data.value == 19

dut.stream_in_data.value = Force(0)
dut.stream_in_data.value = Release()
await Timer(10, "ns")
assert dut.stream_in_data.value == dut.stream_out_data_comb.value
assert dut.stream_in_data.value == 0

0 comments on commit 9c97de5

Please sign in to comment.