vunit-python-bridge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. vunit_python_bridge/__init__.py +92 -0
  2. vunit_python_bridge/bin/vunit_python_bridge-cp310-win_amd64.dll +0 -0
  3. vunit_python_bridge/bin/vunit_python_bridge-cp311-win_amd64.dll +0 -0
  4. vunit_python_bridge/bin/vunit_python_bridge-cp312-win_amd64.dll +0 -0
  5. vunit_python_bridge/bin/vunit_python_bridge-cp313-win_amd64.dll +0 -0
  6. vunit_python_bridge/bin/vunit_python_bridge-cp314-win_amd64.dll +0 -0
  7. vunit_python_bridge/bridge.py +171 -0
  8. vunit_python_bridge/foreign_application.py +90 -0
  9. vunit_python_bridge/hdl/src/python_bridge_pkg.vhd.in +91 -0
  10. vunit_python_bridge/hdl/src/python_context.vhd +13 -0
  11. vunit_python_bridge/hdl/src/python_ffi_pkg_bridge.vhd +507 -0
  12. vunit_python_bridge/hdl/src/python_pkg.vhd +1087 -0
  13. vunit_python_bridge/hdl/src/python_pkg_vhpi.vhd +270 -0
  14. vunit_python_bridge/hdl/tools/generate_python_pkg.py +386 -0
  15. vunit_python_bridge/hdl/tools/python_pkg.vhd.in +765 -0
  16. vunit_python_bridge/native/arguments.c +191 -0
  17. vunit_python_bridge/native/bridge.h +117 -0
  18. vunit_python_bridge/native/config.c +201 -0
  19. vunit_python_bridge/native/error.c +120 -0
  20. vunit_python_bridge/native/fli.c +98 -0
  21. vunit_python_bridge/native/interpreter.c +271 -0
  22. vunit_python_bridge/native/operations.c +239 -0
  23. vunit_python_bridge/native/vhpi/python_pkg.c +125 -0
  24. vunit_python_bridge/native/vhpi/python_pkg.h +31 -0
  25. vunit_python_bridge/native/vhpi/python_pkg_vhpi.c +366 -0
  26. vunit_python_bridge/native_library.py +455 -0
  27. vunit_python_bridge/runtime.py +645 -0
  28. vunit_python_bridge/simulator_hooks.py +141 -0
  29. vunit_python_bridge/vunit_pkg.toml +17 -0
  30. vunit_python_bridge-0.1.0.dist-info/METADATA +116 -0
  31. vunit_python_bridge-0.1.0.dist-info/RECORD +34 -0
  32. vunit_python_bridge-0.1.0.dist-info/WHEEL +5 -0
  33. vunit_python_bridge-0.1.0.dist-info/licenses/LICENSE +373 -0
  34. vunit_python_bridge-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,507 @@
1
+ -- This Source Code Form is subject to the terms of the Mozilla Public
2
+ -- License, v. 2.0. If a copy of the MPL was not distributed with this file,
3
+ -- You can obtain one at http://mozilla.org/MPL/2.0/.
4
+ --
5
+ -- Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com
6
+ --
7
+ -- The foreign language interface of the Python package for NVC and GHDL,
8
+ -- implemented with the VUnit Python bridge (src/vunit_python_bridge). It provides
9
+ -- the same subprograms as the FLI and VHPI variants but over the private
10
+ -- python_bridge_pkg, which the setup function of the package generates.
11
+ --
12
+ -- Deviations from the other variants:
13
+ -- * The subprograms are impure since they call impure foreign subprograms.
14
+ -- * Errors are reported as failures on the logger of the session they were
15
+ -- performed in rather than aborting the simulation directly, which makes
16
+ -- them observable from VHDL.
17
+ -- * Sessions other than the default one are supported.
18
+ --
19
+ -- The p_ prefixed declarations are the private primitives the bridge
20
+ -- operations of python_pkg are built on. They are not part of the API. The FLI and VHPI
21
+ -- variants of this package declare the same primitives and implement them by
22
+ -- reporting a failure.
23
+
24
+ library vunit_lib;
25
+ use vunit_lib.id_pkg.all;
26
+ use vunit_lib.integer_array_pkg.all;
27
+ use vunit_lib.integer_vector_ptr_pkg.all;
28
+ use vunit_lib.logger_pkg.all;
29
+ use work.python_bridge_pkg.all;
30
+
31
+ package python_ffi_pkg is
32
+ -- The identity of the Python interface. It is the identity of
33
+ -- python_logger and the parent of the identities of the sessions created
34
+ -- from a name.
35
+ constant p_python_id : id_t := get_id("python_bridge:python");
36
+
37
+ -- Logger of the Python interface. It is the parent of the session loggers
38
+ -- an operation reports its errors on, so log levels and log handler
39
+ -- settings made on python_logger apply to all sessions. It is also the
40
+ -- logger used to report what has no session of its own: an argument value
41
+ -- that cannot be converted to Python and the transfer of an integer_array_t.
42
+ constant python_logger : logger_t := get_logger(p_python_id);
43
+
44
+ -- A session is a Python namespace. Sessions are isolated from each other
45
+ -- and have an identity, which is what they are created from:
46
+ --
47
+ -- constant golden_model : python_session_t := new_session("golden_model");
48
+ --
49
+ -- new_session(name) creates the identity name under the identity of the
50
+ -- Python interface, while new_session(id) takes an existing identity, which
51
+ -- puts the session anywhere in the identity tree, for example under the
52
+ -- identity of what it serves. Two sessions with the same identity are the
53
+ -- same session.
54
+ --
55
+ -- The Python namespace is created on first use and the operations of a
56
+ -- session report their errors on get_logger(get_id(session)). The default
57
+ -- session is the __main__ namespace.
58
+ type python_session_t is record
59
+ p_data : integer_vector_ptr_t;
60
+ end record;
61
+
62
+ impure function new_session(name : string) return python_session_t;
63
+ impure function new_session(id : id_t) return python_session_t;
64
+
65
+ -- The identity of a session, which is what two sessions are compared by
66
+ impure function get_id(session : python_session_t) return id_t;
67
+
68
+ -- The name of the identity of a session, without hierarchy
69
+ impure function name(session : python_session_t) return string;
70
+
71
+ -- The session the operations are performed in when no other one is given.
72
+ -- It is created the way new_session does, which cannot be called here since
73
+ -- its body is not elaborated until the package body is.
74
+ constant default_session : python_session_t :=
75
+ (p_data => new_integer_vector_ptr(1, value => to_integer(get_id("default", parent => p_python_id))));
76
+
77
+ -- Start the embedded Python interpreter eagerly. Optional and idempotent:
78
+ -- without it the interpreter starts on first use.
79
+ procedure python_setup;
80
+
81
+ -- Release the values staged for integer_array_t arguments. Optional: they
82
+ -- are released when the simulator process ends. The interpreter is not
83
+ -- finalized, so later operations keep working.
84
+ procedure python_cleanup;
85
+
86
+ -- Execute Python source code in the namespace of a session
87
+ procedure exec(code : string; session : python_session_t := default_session);
88
+
89
+ -- Evaluate a Python expression and convert its value to a VHDL type
90
+ impure function eval_integer(expr : string; session : python_session_t := default_session) return integer;
91
+ alias eval is eval_integer[string, python_session_t return integer];
92
+
93
+ impure function eval_real(expr : string; session : python_session_t := default_session) return real;
94
+ alias eval is eval_real[string, python_session_t return real];
95
+
96
+ impure function eval_integer_vector(
97
+ expr : string; session : python_session_t := default_session
98
+ ) return integer_vector;
99
+ alias eval is eval_integer_vector[string, python_session_t return integer_vector];
100
+
101
+ impure function eval_real_vector(
102
+ expr : string; session : python_session_t := default_session
103
+ ) return real_vector;
104
+ alias eval is eval_real_vector[string, python_session_t return real_vector];
105
+
106
+ impure function eval_string(expr : string; session : python_session_t := default_session) return string;
107
+ alias eval is eval_string[string, python_session_t return string];
108
+
109
+ -----------------------------------------------------------------------------
110
+ -- Private, the primitives the bridge operations of python_pkg are built on
111
+ -----------------------------------------------------------------------------
112
+ -- Result kinds, must match src/vunit_python_bridge/runtime.py
113
+ constant p_kind_integer : integer := 0;
114
+ constant p_kind_real : integer := 1;
115
+ constant p_kind_boolean : integer := 2;
116
+ constant p_kind_string : integer := 3;
117
+ constant p_kind_std_ulogic : integer := 4;
118
+ constant p_kind_std_ulogic_vector : integer := 5;
119
+ constant p_kind_signed : integer := 6;
120
+ constant p_kind_unsigned : integer := 7;
121
+ constant p_kind_integer_array : integer := 8;
122
+ constant p_kind_integer_vector : integer := 9;
123
+ constant p_kind_real_vector : integer := 10;
124
+
125
+ -- Names of the operations, used in the error messages
126
+ impure function p_exec_operation(session : python_session_t := default_session) return string;
127
+ impure function p_exec_file_operation(
128
+ file_name : string; session : python_session_t := default_session
129
+ ) return string;
130
+ impure function p_eval_operation(expr : string; session : python_session_t := default_session) return string;
131
+
132
+ -- The error of the last failed bridge operation, typically a Python traceback
133
+ impure function p_error_text return string;
134
+
135
+ -- Report a failed operation (status /= 0) with its error. True if it succeeded.
136
+ impure function p_succeeded(
137
+ status : integer; operation : string; logger : logger_t := python_logger
138
+ ) return boolean;
139
+
140
+ -- Transfer a string to the bridge buffer. Returns the bridge status.
141
+ impure function p_send(text : string) return integer;
142
+
143
+ -- Start an operation in a session
144
+ impure function p_begin(session : python_session_t; operation : string) return boolean;
145
+
146
+ -- Execute text as Python source code (is_file = 0) or as a Python file name (is_file = 1)
147
+ impure function p_exec(
148
+ text : string;
149
+ is_file : integer;
150
+ operation : string;
151
+ session : python_session_t := default_session
152
+ ) return boolean;
153
+
154
+ -- Execute the Python file with the given name
155
+ impure function p_exec_file(
156
+ file_name : string; session : python_session_t := default_session
157
+ ) return boolean;
158
+
159
+ -- Evaluate expr and convert the value to the VHDL type given by kind. width
160
+ -- is the length of a std_ulogic_vector, signed or unsigned result, -1 when
161
+ -- it is not known.
162
+ impure function p_eval(
163
+ expr : string;
164
+ kind : integer;
165
+ width : integer;
166
+ operation : string;
167
+ session : python_session_t := default_session
168
+ ) return boolean;
169
+
170
+ -- The result of the last evaluation
171
+ impure function p_result_integer return integer;
172
+ impure function p_result_string return string;
173
+ impure function p_result_integer_vector return integer_vector;
174
+ impure function p_result_real_vector return real_vector;
175
+ impure function p_result_integer_array return integer_array_t;
176
+
177
+ -- Transfer an integer_array_t to Python and return the id it is staged
178
+ -- under, -1 on failure. Python source code refers to the value as
179
+ -- __vunit__.staged(<id>).
180
+ impure function p_stage_array(arr : integer_array_t; operation : string) return integer;
181
+ end package;
182
+
183
+ package body python_ffi_pkg is
184
+ -----------------------------------------------------------------------------
185
+ -- Sessions
186
+ -----------------------------------------------------------------------------
187
+ impure function new_session(name : string) return python_session_t is
188
+ begin
189
+ return new_session(get_id(name, parent => p_python_id));
190
+ end;
191
+
192
+ impure function new_session(id : id_t) return python_session_t is
193
+ begin
194
+ return (p_data => new_integer_vector_ptr(1, value => to_integer(id)));
195
+ end;
196
+
197
+ impure function get_id(session : python_session_t) return id_t is
198
+ begin
199
+ return to_id(get(session.p_data, 0));
200
+ end;
201
+
202
+ impure function name(session : python_session_t) return string is
203
+ begin
204
+ return name(get_id(session));
205
+ end;
206
+
207
+ impure function p_is_default(session : python_session_t) return boolean is
208
+ begin
209
+ return get_id(session) = get_id(default_session);
210
+ end;
211
+
212
+ -----------------------------------------------------------------------------
213
+ -- Operation names
214
+ -----------------------------------------------------------------------------
215
+ impure function p_exec_operation(session : python_session_t := default_session) return string is
216
+ begin
217
+ if p_is_default(session) then
218
+ return "exec";
219
+ end if;
220
+ return "exec(session => """ & name(session) & """)";
221
+ end;
222
+
223
+ impure function p_exec_file_operation(
224
+ file_name : string; session : python_session_t := default_session
225
+ ) return string is
226
+ begin
227
+ if p_is_default(session) then
228
+ return "exec_file(""" & file_name & """)";
229
+ end if;
230
+ return "exec_file(""" & file_name & """, session => """ & name(session) & """)";
231
+ end;
232
+
233
+ impure function p_eval_operation(expr : string; session : python_session_t := default_session) return string is
234
+ begin
235
+ if p_is_default(session) then
236
+ return "eval(""" & expr & """)";
237
+ end if;
238
+ return "eval(""" & expr & """, session => """ & name(session) & """)";
239
+ end;
240
+
241
+ -----------------------------------------------------------------------------
242
+ -- Transfers through the bridge
243
+ -----------------------------------------------------------------------------
244
+ impure function p_error_text return string is
245
+ constant len : natural := vpy_error_length;
246
+ variable result : string(1 to len);
247
+ variable chunk : string_chunk_t;
248
+ variable offset, n : natural := 0;
249
+ begin
250
+ while offset < len loop
251
+ n := minimum(chunk_length, len - offset);
252
+ vpy_error_read(chunk, offset, n);
253
+ result(offset + 1 to offset + n) := chunk(1 to n);
254
+ offset := offset + n;
255
+ end loop;
256
+ return result;
257
+ end;
258
+
259
+ impure function p_succeeded(
260
+ status : integer; operation : string; logger : logger_t := python_logger
261
+ ) return boolean is
262
+ begin
263
+ if status = 0 then
264
+ return true;
265
+ end if;
266
+ failure(logger, operation & " failed:" & LF & p_error_text);
267
+ return false;
268
+ end;
269
+
270
+ impure function p_send(text : string) return integer is
271
+ alias normalized : string(1 to text'length) is text;
272
+ variable chunk : string_chunk_t;
273
+ variable offset, n : natural := 0;
274
+ variable status : integer;
275
+ begin
276
+ status := vpy_buffer_clear;
277
+ while offset < text'length and status = 0 loop
278
+ n := minimum(chunk_length, text'length - offset);
279
+ chunk(1 to n) := normalized(offset + 1 to offset + n);
280
+ status := vpy_buffer_append(chunk, n);
281
+ offset := offset + n;
282
+ end loop;
283
+ return status;
284
+ end;
285
+
286
+ -- The full name of the identity of the session is the key of its Python
287
+ -- namespace, so that two sessions with the same name but different
288
+ -- identities are namespaces of their own.
289
+ impure function p_begin(session : python_session_t; operation : string) return boolean is
290
+ constant logger : logger_t := get_logger(get_id(session));
291
+ begin
292
+ return p_succeeded(p_send(full_name(get_id(session))), operation, logger)
293
+ and p_succeeded(vpy_begin, operation, logger);
294
+ end;
295
+
296
+ impure function p_exec(
297
+ text : string;
298
+ is_file : integer;
299
+ operation : string;
300
+ session : python_session_t := default_session
301
+ ) return boolean is
302
+ constant logger : logger_t := get_logger(get_id(session));
303
+ begin
304
+ return p_begin(session, operation)
305
+ and p_succeeded(p_send(text), operation, logger)
306
+ and p_succeeded(vpy_execute(is_file), operation, logger);
307
+ end;
308
+
309
+ impure function p_exec_file(
310
+ file_name : string; session : python_session_t := default_session
311
+ ) return boolean is
312
+ begin
313
+ return p_exec(file_name, 1, p_exec_file_operation(file_name, session), session);
314
+ end;
315
+
316
+ impure function p_eval(
317
+ expr : string;
318
+ kind : integer;
319
+ width : integer;
320
+ operation : string;
321
+ session : python_session_t := default_session
322
+ ) return boolean is
323
+ constant logger : logger_t := get_logger(get_id(session));
324
+ begin
325
+ return p_begin(session, operation)
326
+ and p_succeeded(p_send(expr), operation, logger)
327
+ and p_succeeded(vpy_eval(kind, width), operation, logger);
328
+ end;
329
+
330
+ -----------------------------------------------------------------------------
331
+ -- Results
332
+ -----------------------------------------------------------------------------
333
+ impure function p_result_integer return integer is
334
+ begin
335
+ return vpy_result_integer;
336
+ end;
337
+
338
+ impure function p_result_string return string is
339
+ constant len : natural := vpy_result_meta(0);
340
+ variable result : string(1 to len);
341
+ variable chunk : string_chunk_t;
342
+ variable offset, n : natural := 0;
343
+ begin
344
+ while offset < len loop
345
+ n := minimum(chunk_length, len - offset);
346
+ vpy_result_read_string(chunk, offset, n);
347
+ result(offset + 1 to offset + n) := chunk(1 to n);
348
+ offset := offset + n;
349
+ end loop;
350
+ return result;
351
+ end;
352
+
353
+ impure function p_result_integer_vector return integer_vector is
354
+ constant len : natural := vpy_result_meta(0);
355
+ variable result : integer_vector(0 to len - 1);
356
+ variable chunk : integer_chunk_t;
357
+ variable offset, n : natural := 0;
358
+ begin
359
+ while offset < len loop
360
+ n := minimum(chunk_length, len - offset);
361
+ vpy_result_read_integers(chunk, offset, n);
362
+ result(offset to offset + n - 1) := chunk(0 to n - 1);
363
+ offset := offset + n;
364
+ end loop;
365
+ return result;
366
+ end;
367
+
368
+ impure function p_result_real_vector return real_vector is
369
+ constant len : natural := vpy_result_meta(0);
370
+ variable result : real_vector(0 to len - 1);
371
+ variable chunk : real_chunk_t;
372
+ variable offset, n : natural := 0;
373
+ begin
374
+ while offset < len loop
375
+ n := minimum(chunk_length, len - offset);
376
+ vpy_result_read_reals(chunk, offset, n);
377
+ result(offset to offset + n - 1) := chunk(0 to n - 1);
378
+ offset := offset + n;
379
+ end loop;
380
+ return result;
381
+ end;
382
+
383
+ impure function p_result_integer_array return integer_array_t is
384
+ constant len : natural := vpy_result_meta(0);
385
+ variable result : integer_array_t := new_3d(
386
+ width => vpy_result_meta(1),
387
+ height => vpy_result_meta(2),
388
+ depth => vpy_result_meta(3),
389
+ bit_width => vpy_result_meta(4),
390
+ is_signed => vpy_result_meta(5) /= 0
391
+ );
392
+ variable chunk : integer_chunk_t;
393
+ variable offset, n : natural := 0;
394
+ begin
395
+ while offset < len loop
396
+ n := minimum(chunk_length, len - offset);
397
+ vpy_result_read_integers(chunk, offset, n);
398
+ for idx in 0 to n - 1 loop
399
+ set(result, offset + idx, chunk(idx));
400
+ end loop;
401
+ offset := offset + n;
402
+ end loop;
403
+ return result;
404
+ end;
405
+
406
+ -----------------------------------------------------------------------------
407
+ -- Values transferred to Python
408
+ -----------------------------------------------------------------------------
409
+ impure function p_stage_array(arr : integer_array_t; operation : string) return integer is
410
+ constant len : natural := length(arr);
411
+ variable chunk : integer_chunk_t;
412
+ variable offset, n : natural := 0;
413
+ variable staged_id : integer;
414
+ begin
415
+ if not p_succeeded(
416
+ vpy_push_array(len, width(arr), height(arr), depth(arr), bit_width(arr), boolean'pos(is_signed(arr))),
417
+ operation
418
+ ) then
419
+ return -1;
420
+ end if;
421
+
422
+ while offset < len loop
423
+ n := minimum(chunk_length, len - offset);
424
+ for idx in 0 to n - 1 loop
425
+ chunk(idx) := get(arr, offset + idx);
426
+ end loop;
427
+ if not p_succeeded(vpy_array_write(chunk, n), operation) then
428
+ return -1;
429
+ end if;
430
+ offset := offset + n;
431
+ end loop;
432
+
433
+ staged_id := vpy_stage;
434
+ if staged_id < 1 then
435
+ failure(python_logger, operation & " failed:" & LF & p_error_text);
436
+ return -1;
437
+ end if;
438
+ return staged_id;
439
+ end;
440
+
441
+ -----------------------------------------------------------------------------
442
+ -- The API
443
+ -----------------------------------------------------------------------------
444
+ procedure python_setup is
445
+ variable ok : boolean;
446
+ begin
447
+ ok := p_succeeded(vpy_setup, "python_setup");
448
+ end;
449
+
450
+ procedure python_cleanup is
451
+ variable ok : boolean;
452
+ begin
453
+ ok := p_succeeded(vpy_cleanup, "python_cleanup");
454
+ end;
455
+
456
+ procedure exec(code : string; session : python_session_t := default_session) is
457
+ variable ok : boolean;
458
+ begin
459
+ ok := p_exec(code, 0, p_exec_operation(session), session);
460
+ end;
461
+
462
+ impure function eval_integer(expr : string; session : python_session_t := default_session) return integer is
463
+ begin
464
+ if p_eval(expr, p_kind_integer, -1, p_eval_operation(expr, session), session) then
465
+ return vpy_result_integer;
466
+ end if;
467
+ return integer'low;
468
+ end;
469
+
470
+ impure function eval_real(expr : string; session : python_session_t := default_session) return real is
471
+ begin
472
+ if p_eval(expr, p_kind_real, -1, p_eval_operation(expr, session), session) then
473
+ return vpy_result_real;
474
+ end if;
475
+ return 0.0;
476
+ end;
477
+
478
+ impure function eval_integer_vector(
479
+ expr : string; session : python_session_t := default_session
480
+ ) return integer_vector is
481
+ constant no_integers : integer_vector(0 downto 1) := (others => 0);
482
+ begin
483
+ if p_eval(expr, p_kind_integer_vector, -1, p_eval_operation(expr, session), session) then
484
+ return p_result_integer_vector;
485
+ end if;
486
+ return no_integers;
487
+ end;
488
+
489
+ impure function eval_real_vector(
490
+ expr : string; session : python_session_t := default_session
491
+ ) return real_vector is
492
+ constant no_reals : real_vector(0 downto 1) := (others => 0.0);
493
+ begin
494
+ if p_eval(expr, p_kind_real_vector, -1, p_eval_operation(expr, session), session) then
495
+ return p_result_real_vector;
496
+ end if;
497
+ return no_reals;
498
+ end;
499
+
500
+ impure function eval_string(expr : string; session : python_session_t := default_session) return string is
501
+ begin
502
+ if p_eval(expr, p_kind_string, -1, p_eval_operation(expr, session), session) then
503
+ return p_result_string;
504
+ end if;
505
+ return "";
506
+ end;
507
+ end package body;