Skip to content

Commit

Permalink
vpi packed structs
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew Nolte committed Mar 7, 2024
1 parent 318ec4b commit 2e84535
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 3 deletions.
48 changes: 45 additions & 3 deletions src/cocotb/handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,13 +539,13 @@ def value(self):

@value.setter
def value(self, value):
if self.is_const:
if self._is_const:
raise TypeError(f"{self._path} is constant")
self._set_value(value, cocotb.scheduler._schedule_write)

def setimmediatevalue(self, value):
"""Assign a value to this simulation object immediately."""
if self.is_const:
if self._is_const:
raise TypeError(f"{self._path} is constant")

def _call_now(handle, f, *args):
Expand All @@ -554,7 +554,7 @@ def _call_now(handle, f, *args):
self._set_value(value, _call_now)

@cached_property
def is_const(self) -> bool:
def _is_const(self) -> bool:
"""``True`` if the simulator object is immutable, e.g. a Verilog parameter or VHDL constant or generic."""
return self._handle.get_const()

Expand Down Expand Up @@ -829,6 +829,47 @@ def value(self) -> LogicArray:
binstr = self._handle.get_signal_val_binstr()
return LogicArray(binstr)

@property # alias for value
def _value(self) -> LogicArray:
return LogicObject.value.fget(self)

@_value.setter
def _value(self, value: LogicArray) -> None:
LogicObject.value.fset(self, value)


class PackedStructObject(HierarchyObject, LogicObject):
"""
Restrictions:
- _value can be used instead of value if defines, but value works if no children are names value
- no children named setimmediatevalue
"""

def __getattribute__(self, name: str) -> SimHandleBase:
"""
Support getting/setting value with _value instead
"""
if name.startswith("_"):
return object.__getattribute__(self, name)
try:
return self[name]
except KeyError as e:
if name == "value":
return LogicObject.__getattribute__(self, "value")
raise AttributeError(str(e)) from None

def __setattr__(self, name: str, value: Any) -> None:
if name == "value":
self._discover_all()
if "value" not in self._sub_handles:
return LogicObject.__setattr__(self, "value", value)
else:
raise AttributeError(
f"Attempting to set packed struct {self._path}.value with child named value. Use ._value ="
)

return LogicObject.__setattr__(self, name, value)


class RealObject(ModifiableObject):
"""Specific object handle for Real signals and variables."""
Expand Down Expand Up @@ -997,6 +1038,7 @@ def value(self) -> bytes:
_type2cls = {
simulator.MODULE: HierarchyObject,
simulator.STRUCTURE: HierarchyObject,
simulator.PACKED_STRUCTURE: PackedStructObject,
simulator.REG: LogicObject,
simulator.NET: LogicObject,
simulator.NETARRAY: NonHierarchyIndexableObject,
Expand Down
1 change: 1 addition & 0 deletions src/cocotb/share/include/gpi.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ typedef enum gpi_objtype_e {
GPI_STRING = 11,
GPI_GENARRAY = 12,
GPI_PACKAGE = 13,
GPI_PACKED_STRUCTURE = 8,
} gpi_objtype_t;

// When iterating, we can chose to either get child objects, drivers or loads
Expand Down
2 changes: 2 additions & 0 deletions src/cocotb/share/lib/simulator/simulatormodule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -850,6 +850,8 @@ static int add_module_constants(PyObject *simulator) {
PyModule_AddIntConstant(simulator, "NETARRAY", GPI_ARRAY) < 0 ||
PyModule_AddIntConstant(simulator, "ENUM", GPI_ENUM) < 0 ||
PyModule_AddIntConstant(simulator, "STRUCTURE", GPI_STRUCTURE) < 0 ||
PyModule_AddIntConstant(simulator, "PACKED_STRUCTURE",
GPI_PACKED_STRUCTURE) < 0 ||
PyModule_AddIntConstant(simulator, "REAL", GPI_REAL) < 0 ||
PyModule_AddIntConstant(simulator, "INTEGER", GPI_INTEGER) < 0 ||
PyModule_AddIntConstant(simulator, "STRING", GPI_STRING) < 0 ||
Expand Down
5 changes: 5 additions & 0 deletions src/cocotb/share/lib/vpi/VpiImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,11 @@ GpiObjHdl *VpiImpl::create_gpi_obj_from_handle(vpiHandle new_hdl,
case vpiStructNet:
case vpiUnionVar:
case vpiUnionNet:
if (vpi_get(vpiPacked, new_hdl)) {
new_obj =
new VpiStructObjHdl(this, new_hdl, GPI_PACKED_STRUCTURE);
break;
}
new_obj = new VpiObjHdl(this, new_hdl, to_gpi_objtype(type));
break;
case vpiModule:
Expand Down
7 changes: 7 additions & 0 deletions src/cocotb/share/lib/vpi/VpiImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ class VpiSignalObjHdl : public GpiSignalObjHdl {
VpiValueCbHdl m_either_cb;
};

class VpiStructObjHdl : public VpiSignalObjHdl {
public:
VpiStructObjHdl(GpiImplInterface *impl, vpiHandle hdl,
gpi_objtype_t objtype)
: VpiSignalObjHdl(impl, hdl, objtype, false) {}
};

class VpiIterator : public GpiIterator {
public:
VpiIterator(GpiImplInterface *impl, GpiObjHdl *hdl);
Expand Down
1 change: 1 addition & 0 deletions src/cocotb/simulator.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ REAL: int
REG: int
STRING: int
STRUCTURE: int
PACKED_STRUCTURE: int
UNKNOWN: int

class gpi_cb_hdl:
Expand Down
10 changes: 10 additions & 0 deletions tests/designs/sample_module/sample_module.sv
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ typedef struct packed
logic a_in;
logic b_out;
} test_if;

typedef struct packed
{
logic val_a;
logic val_b;
logic value;
} test_struct;


`endif

module sample_module #(
Expand All @@ -54,6 +63,7 @@ module sample_module #(
output real stream_out_real,
output integer stream_out_int,
input test_if inout_if,
input test_struct my_struct,
input string stream_in_string,
`endif
input [7:0] stream_in_data,
Expand Down
19 changes: 19 additions & 0 deletions tests/test_cases/test_struct/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright cocotb contributors
# Licensed under the Revised BSD License, see LICENSE for details.
# SPDX-License-Identifier: BSD-3-Clause


TOPLEVEL_LANG ?= verilog

ifneq ($(TOPLEVEL_LANG),verilog)

all:
@echo "Skipping test due to TOPLEVEL_LANG=$(TOPLEVEL_LANG) not being verilog"
clean::

else

include ../../designs/sample_module/Makefile
MODULE:=test_struct

endif
87 changes: 87 additions & 0 deletions tests/test_cases/test_struct/test_struct.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Copyright cocotb contributors
# Licensed under the Revised BSD License, see LICENSE for details.
# SPDX-License-Identifier: BSD-3-Clause

import contextlib

import cocotb
from cocotb.triggers import Timer

test_dec = cocotb.test(
expect_error=AttributeError
if cocotb.SIM_NAME.lower().startswith(("icarus", "ghdl", "riviera", "nvc"))
else (),
expect_fail=cocotb.SIM_NAME.lower().startswith("modelsim"),
skip=cocotb.SIM_NAME.lower().startswith("verilator"),
)


@contextlib.contextmanager
def assert_raises(exc_type):
try:
yield
except exc_type as exc:
cocotb.log.info(f" {exc_type.__name__} raised as expected: {exc}")
else:
raise AssertionError(f"{exc_type.__name__} was not raised")


@test_dec

Check failure on line 29 in tests/test_cases/test_struct/test_struct.py

View workflow job for this annotation

GitHub Actions / Regression Tests / riviera|2023.10|verilog|ubuntu-20.04|3.8

Test test_struct:test_struct_format failed
async def test_struct_format(dut):
"""Test that the correct objects are returned for a struct"""
s = dut.inout_if
assert repr(s) == "PackedStructObject(sample_module.inout_if)"

# use _value or value to access signal
cocotb.log.info(f"{s._value=}")
assert repr(s._value) == "LogicArray('ZZ', Range(1, 'downto', 0))"
assert repr(s.value) == "LogicArray('ZZ', Range(1, 'downto', 0))"

cocotb.log.info(f"{s.a_in=}")
assert repr(s.a_in) == "LogicObject(sample_module.inout_if.a_in)"

cocotb.log.info(f"{s.b_out=}")
assert repr(s.b_out) == "LogicObject(sample_module.inout_if.b_out)"


@test_dec

Check failure on line 47 in tests/test_cases/test_struct/test_struct.py

View workflow job for this annotation

GitHub Actions / Regression Tests / riviera|2023.10|verilog|ubuntu-20.04|3.8

Test test_struct:test_struct_setting failed
async def test_struct_setting(dut):
"""Test getting and setting setting the value of an entire struct"""

assert dut.inout_if._value.binstr == "ZZ"
assert dut.inout_if.value.binstr == "ZZ"

# test struct write -> individual signals
dut.inout_if._value = 0
await Timer(1000, "ns")
assert dut.inout_if.value.binstr == "00"
assert dut.inout_if._value.binstr == "00"
assert dut.inout_if.a_in.value == 0
assert dut.inout_if.b_out.value == 0

# test signal write -> struct value
dut.inout_if.a_in.value = 1
await Timer(1000, "ns")
assert dut.inout_if._value.binstr == "10"

dut.inout_if.b_out.value = 1
await Timer(1000, "ns")
assert dut.inout_if._value.binstr == "11"


@test_dec

Check failure on line 72 in tests/test_cases/test_struct/test_struct.py

View workflow job for this annotation

GitHub Actions / Regression Tests / riviera|2023.10|verilog|ubuntu-20.04|3.8

Test test_struct:test_value failed
async def test_value(dut):
"""
Test a struct with a field named value works properly
"""
s = dut.my_struct

with assert_raises(AttributeError):
s.value = 0

assert repr(s.value) == "LogicObject(sample_module.my_struct.value)"
assert repr(s._value) == "LogicArray('ZZZ', Range(2, 'downto', 0))"

s._value = 0
await Timer(1000, "ns")
assert s._value.binstr == "000"

0 comments on commit 2e84535

Please sign in to comment.