Skip to content

Commit 1631a7c

Browse files
authored
Add support for in/out ports (#46)
1 parent d328ce9 commit 1631a7c

File tree

9 files changed

+189
-12
lines changed

9 files changed

+189
-12
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ pip install cocotb
3131

3232
You will also need your favorite SystemVerilog simulator to do cosimulation between ROHD and SystemVerilog modules. ROHD Cosim does *not* do any SystemVerilog parsing or SystemVerilog simulation itself.
3333

34+
See the section on [support and limitations](#support--limitations) for more details on simulators, versions, limitations, etc.
35+
3436
## Using ROHD Cosim
3537

3638
There are two steps to using ROHD Cosim:
@@ -153,6 +155,16 @@ The diagram below shows how the port configuration connects to your simulation.
153155

154156
![Port Config Diagram](https://github.com/intel/rohd-cosim/raw/main/doc/diagrams/port.png)
155157

158+
## Support & Limitations
159+
160+
ROHD Cosim depends on cross-compatibility with SystemVerilog simulators and the libraries from cocotb that enable VPI-based communication between them. Because of this, there are some limitations which may be version and simulator specific, as well as some fundamental limitations that have to do with the communication mechanisms.
161+
162+
- The simulation must occur over time. That is, it is not possible to have a "purely combinational" block of logic, change an input, and view an instantaneous output change, as you normally can with ROHD modules. This is because coordination between the ROHD simulation and the SystemVerilog simulator is coordinated by `Simulator` phasing. Therefore, changes on the output of a SystemVerilog cosimulated module will not update until some time has passed.
163+
- ROHD Cosim can support simulators supported by cocotb, but has only been tested with those officially listed in the [`SystemVerilogSimulator` enum](https://intel.github.io/rohd-cosim/rohd_cosim/SystemVerilogSimulator.html).
164+
- Cross-version compatibility between ROHD Cosim, cocotb, and SystemVerilog simulators can, unfortunatley, be delicate. Check the [simulator support documentation from cocotb](https://docs.cocotb.org/en/stable/simulator_support.html) for more details about which versions of which simulators will work well with which versions of cocotb (and thus ROHD Cosim).
165+
- Different SystemVerilog simulators have different limitations and capabilities, and thus those features would be limited in cosimulation as well. For example, Verilator does not support X/Z values.
166+
- In/Out ports and bidirectional wires are supported in ROHD Cosim, as they are in ROHD, however contention may not be calculated at these port boundaries in a realistic way. This has to do with how ROHD Cosim sends and receives updates with the SystemVerilog simulator. If the SystemVerilog simulator resolves a value or contention on an inout port, it may not be possible for ROHD to determine whether the contention can/should break, for example.
167+
156168
----------------
157169
2022 September 9
158170
Author: Max Korbel <<[email protected]>>

lib/src/configs/cosim_port_config.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,13 @@ class CosimPortConfig extends CosimConfig {
3434
Future<CosimConnection> connect() async {
3535
// ignore: close_sinks
3636
final socket = await Socket.connect(InternetAddress.loopbackIPv4, port)
37-
// ignore: avoid_types_on_closure_parameters
3837
.catchError((Object error, StackTrace stackTrace) {
3938
print('Caught exception during socket connection: $error');
4039
print('> Stack trace:\n$stackTrace');
4140
// ignore: only_throw_errors
4241
throw error;
4342
});
44-
// ignore: avoid_types_on_closure_parameters
43+
4544
socket.handleError((Object error, StackTrace stackTrace) {
4645
print('Caught exception from socket via port configuration: $error');
4746
print('> Stack trace:\n$stackTrace');

lib/src/configs/cosim_wrap_config.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ enum SystemVerilogSimulator {
2424
/// Known limitations to be aware of:
2525
/// - Verilator does not support invalid (`x`, `z`) values in the simulation.
2626
/// - Support for unpacked array ports is limited.
27+
/// - Support for inout connections is limited.
2728
verilator,
2829
}
2930

@@ -145,6 +146,7 @@ class CosimWrapConfig extends CosimProcessConfig {
145146
{
146147
...registreeEntry.value.inputs,
147148
...registreeEntry.value.outputs,
149+
...registreeEntry.value.inOuts,
148150
}.map((key, value) => MapEntry(key, '')),
149151
)),
150152
if (dumpWavesString != null) dumpWavesString,

lib/src/cosim.dart

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,6 @@ mixin Cosim on ExternalSystemVerilogModule {
192192
_socket = connection.socket;
193193

194194
// catch errors if the socket shuts down
195-
// ignore: avoid_types_on_closure_parameters
196195
unawaited(_socket!.done.catchError((Object error) {
197196
logger?.info('Encountered error upon socket completion, '
198197
'shutting down cosim: $error');
@@ -208,7 +207,6 @@ mixin Cosim on ExternalSystemVerilogModule {
208207
endCosim();
209208
},
210209
cancelOnError: true,
211-
// ignore: avoid_types_on_closure_parameters
212210
onError: (Object err, StackTrace stackTrace) {
213211
logger?.info(
214212
'Encountered error while listening to socket, ending cosim: $err');
@@ -302,6 +300,28 @@ mixin Cosim on ExternalSystemVerilogModule {
302300
/// Keeps track of whether we need to do an update post-tick.
303301
bool _pendingPostUpdate = false;
304302

303+
/// A mapping from [inOut]s to a "received" driver of them, so that it can
304+
/// calculate if contention has occurred.
305+
final Map<Logic, Logic> _inoutToReceivedInOutDriverMap = {};
306+
307+
/// Returns the driver of an [inOut] to be driven by "received" updates.
308+
Logic _inoutReceivedDriver(String inoutName) {
309+
final io = inOut(inoutName);
310+
if (!_inoutToReceivedInOutDriverMap.containsKey(io)) {
311+
final driverName = 'ioDriverOf_$inoutName';
312+
final driver = io is LogicArray
313+
? LogicArray(
314+
name: driverName,
315+
io.dimensions,
316+
io.elementWidth,
317+
numUnpackedDimensions: io.numUnpackedDimensions)
318+
: Logic(name: driverName, width: io.width);
319+
_inoutToReceivedInOutDriverMap[io] = driver;
320+
io <= driver;
321+
}
322+
return _inoutToReceivedInOutDriverMap[io]!;
323+
}
324+
305325
/// Sets up listeners on ports in both directions with cosimulation for
306326
/// all [_registrees].
307327
static Future<void> _setupPortHandshakes({
@@ -323,19 +343,24 @@ mixin Cosim on ExternalSystemVerilogModule {
323343
' drive this signal: "${event.signalName}"');
324344
}
325345

326-
// handle case where there was an unpacked array (should end with ']')
346+
/// Gets a output or else an inout.
347+
Logic getPort(String name) =>
348+
_registrees[registreeName]!.tryOutput(name) ??
349+
_registrees[registreeName]!._inoutReceivedDriver(name);
350+
327351
if (portName.endsWith(']')) {
352+
// handle case where there was an unpacked array (should end with ']')
328353
final splitPortName = portName.split(RegExp(r'[\[\]]'));
329354
final arrayName = splitPortName[0];
330355
final arrayIndex = int.parse(splitPortName[1]);
331-
(_registrees[registreeName]!.output(arrayName) as LogicArray)
356+
(getPort(arrayName) as LogicArray)
332357
.flattenedUnpacked
333358
.toList()[arrayIndex]
334359
// ignore: unnecessary_null_checks
335360
.put(event.signalValue!);
336361
} else {
337362
// ignore: unnecessary_null_checks
338-
_registrees[registreeName]!.output(portName).put(event.signalValue!);
363+
getPort(portName).put(event.signalValue!);
339364
}
340365
});
341366

@@ -357,7 +382,10 @@ mixin Cosim on ExternalSystemVerilogModule {
357382

358383
for (final registree in _registrees.values) {
359384
// listen to every input of this module
360-
for (final modInput in registree.inputs.values) {
385+
for (final modInput in [
386+
...registree.inputs.values,
387+
...registree.inOuts.values
388+
]) {
361389
if (modInput.width == 0) {
362390
// no need to listen to 0-bit signals, they won't be changing
363391
// (and won't exist in SV)
@@ -568,7 +596,10 @@ async def setup_connections(dut, connector : rohd_connector.RohdConnector):
568596
cocoTbHier += '.${registree.cosimHierarchy}';
569597
}
570598

571-
for (final outputEntry in registree.outputs.entries) {
599+
for (final outputEntry in [
600+
...registree.outputs.entries,
601+
...registree.inOuts.entries
602+
]) {
572603
final outputName = outputEntry.key;
573604
final outputLogic = outputEntry.value;
574605
if (outputLogic.width == 0) {
@@ -592,7 +623,10 @@ async def setup_connections(dut, connector : rohd_connector.RohdConnector):
592623
'))\n');
593624
}
594625
}
595-
for (final inputEntry in registree.inputs.entries) {
626+
for (final inputEntry in [
627+
...registree.inputs.entries,
628+
...registree.inOuts.entries
629+
]) {
596630
final inputName = inputEntry.key;
597631
final inputLogic = inputEntry.value;
598632
if (inputLogic.width == 0) {

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ dependencies:
1919
dev_dependencies:
2020
args: ^2.3.1
2121
test: ^1.17.3
22+

test/cosim_mod_inout.sv

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: BSD-3-Clause
3+
//
4+
// cosim_mod_inout.sv
5+
// A simple SystemVerilog module for testing combinational cosimulation with inout ports.
6+
//
7+
// 2025 January
8+
// Author: Max Korbel <[email protected]>
9+
10+
// A special module for connecting two nets bidirectionally
11+
module net_connect #(parameter WIDTH=1) (w, w);
12+
inout wire[WIDTH-1:0] w;
13+
endmodule
14+
15+
module my_cosim_test_module_nets(
16+
inout wire a,
17+
inout wire b
18+
);
19+
20+
net_connect net_connect(a, b);
21+
22+
endmodule

test/cosim_test.dart

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,28 @@ class DoubleCosimModuleTop extends Module {
6969
}
7070
}
7171

72+
class ExampleTopModuleWithInouts extends Module {
73+
late final ExampleCosimModuleInOut ecm;
74+
75+
ExampleTopModuleWithInouts(LogicNet a, LogicNet b) {
76+
a = addInOut('a', a);
77+
b = addInOut('b', b);
78+
79+
ecm = ExampleCosimModuleInOut(a, b);
80+
}
81+
}
82+
83+
class ExampleCosimModuleInOut extends ExternalSystemVerilogModule with Cosim {
84+
@override
85+
List<String> get verilogSources => ['../../test/cosim_mod_inout.sv'];
86+
87+
ExampleCosimModuleInOut(LogicNet a, LogicNet b)
88+
: super(definitionName: 'my_cosim_test_module_nets') {
89+
addInOut('a', a);
90+
addInOut('b', b);
91+
}
92+
}
93+
7294
Future<void> main() async {
7395
tearDown(() async {
7496
await Simulator.reset();
@@ -99,6 +121,92 @@ Future<void> main() async {
99121
await Simulator.run();
100122
});
101123

124+
if (sim != SystemVerilogSimulator.verilator) {
125+
// verilator does not support inout connections: https://github.com/verilator/verilator/issues/2844
126+
127+
group('inout', () {
128+
test('simple push and check with inout', () async {
129+
final a = LogicNet();
130+
final b = LogicNet();
131+
final mod = ExampleTopModuleWithInouts(a, b);
132+
await mod.build();
133+
134+
await CosimTestingInfrastructure.connectCosim(
135+
'simple_push_n_check_with_inout',
136+
systemVerilogSimulator: sim);
137+
138+
// in the a -> b direction
139+
Simulator.registerAction(2, () {
140+
a.put(1);
141+
});
142+
Simulator.registerAction(3, () {
143+
expect(b.value, equals(LogicValue.one));
144+
});
145+
Simulator.registerAction(4, () {
146+
a.put(0);
147+
});
148+
Simulator.registerAction(5, () {
149+
expect(b.value, equals(LogicValue.zero));
150+
});
151+
152+
// break contention
153+
Simulator.registerAction(6, () {
154+
a.put(LogicValue.z);
155+
});
156+
157+
// in the b -> a direction
158+
Simulator.registerAction(7, () {
159+
b.put(1);
160+
});
161+
Simulator.registerAction(9, () {
162+
expect(a.value, equals(LogicValue.one));
163+
});
164+
Simulator.registerAction(10, () {
165+
b.put(0);
166+
});
167+
Simulator.registerAction(11, () {
168+
expect(a.value, equals(LogicValue.zero));
169+
});
170+
171+
await Simulator.run();
172+
});
173+
174+
test('simple push and check with inout contention', () async {
175+
final a = Logic();
176+
final b = Logic();
177+
final mod = ExampleTopModuleWithInouts(
178+
LogicNet()..gets(a), LogicNet()..gets(b));
179+
await mod.build();
180+
181+
await CosimTestingInfrastructure.connectCosim(
182+
'simple_push_n_check_with_inout_contention',
183+
systemVerilogSimulator: sim);
184+
185+
// in the a -> b direction
186+
Simulator.registerAction(2, () {
187+
a.put(1);
188+
});
189+
Simulator.registerAction(3, () {
190+
expect(mod.inOut('a').value, equals(LogicValue.one));
191+
expect(mod.inOut('b').value, equals(LogicValue.one));
192+
});
193+
194+
// contention
195+
Simulator.registerAction(6, () {
196+
b.put(0);
197+
});
198+
Simulator.registerAction(7, () {
199+
expect(mod.inOut('a').value, equals(LogicValue.x));
200+
expect(mod.inOut('b').value, equals(LogicValue.x));
201+
});
202+
203+
// breaking contention doesn't work, so we're done...
204+
205+
await Simulator.run();
206+
});
207+
});
208+
}
209+
102210
test('comb latency', () async {
103211
final a = Logic();
104212
final mod = ExampleTopModule(a);

test/port_stuff/port_launch.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ Future<void> runCosim(int port,
6565
final mod = BottomMod(a);
6666
await mod.build();
6767

68-
// ignore: dead_code
6968
if (enableLogging) {
7069
Logger.root.level = Level.ALL;
7170
Logger.root.onRecord.listen(print);

test/port_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ void main() async {
8080
final stdoutBuffer = StringBuffer();
8181
unawaited(proc.stdout.transform(utf8.decoder).forEach((msg) {
8282
stdoutBuffer.write(msg);
83-
// ignore: dead_code
83+
8484
if (enableLogging) {
8585
print(msg);
8686
}

0 commit comments

Comments
 (0)