Skip to content

Commit acbbd1c

Browse files
committed
extend(examples/vhdl/array_axis_vcs): integration of foreign C declarations with GHDL
1 parent 97c1538 commit acbbd1c

5 files changed

Lines changed: 336 additions & 1 deletion

File tree

examples/vhdl/array_axis_vcs/run.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,36 @@
1717
"""
1818

1919
from pathlib import Path
20+
from os import popen
2021
from vunit import VUnit
2122

2223
VU = VUnit.from_argv()
2324
VU.add_verification_components()
2425

2526
SRC_PATH = Path(__file__).parent / "src"
2627

27-
VU.add_library("lib").add_source_files([SRC_PATH / "*.vhd", SRC_PATH / "**" / "*.vhd"])
28+
LIB = vu.add_library("lib")
29+
LIB.add_source_files([SRC_PATH / "*.vhd", SRC_PATH / "**" / "*.vhd"])
2830

2931
# vu.set_sim_option('modelsim.init_files.after_load',['runall_addwave.do'])
3032

33+
C_NOBJ = join(src_path, "test", "stubs.o")
34+
C_OBJ = join(src_path, "test", "main.o")
35+
print(
36+
popen(
37+
"gcc -fPIC -rdynamic -c " + join(src_path, "**", "stubs.c") + " -o " + C_NOBJ
38+
).read()
39+
)
40+
print(
41+
popen(
42+
"gcc -fPIC -rdynamic -c " + join(src_path, "**", "main.c") + " -o " + C_OBJ
43+
).read()
44+
)
45+
46+
for tb in lib.get_test_benches(pattern="*tb_py_*", allow_empty=False):
47+
tb.set_sim_option("ghdl.elab_flags", ["-Wl," + C_NOBJ], overwrite=False)
48+
49+
for tb in lib.get_test_benches(pattern="*tb_c_*", allow_empty=False):
50+
tb.set_sim_option("ghdl.elab_flags", ["-Wl," + C_OBJ], overwrite=False)
51+
3152
VU.main()
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <stdint.h>
4+
5+
extern int ghdl_main (int argc, char **argv);
6+
7+
uint8_t *V[2];
8+
uint32_t length = 100;
9+
10+
// get_param is used by GHDL to retrieve parameter values (integers).
11+
uint32_t get_param(uint32_t w) {
12+
uint32_t o = 0;
13+
switch(w) {
14+
case 0 : // buffer length
15+
o = length;
16+
break;;
17+
case 1 : // data width (in bits)
18+
o = 8*sizeof(int32_t);
19+
break;;
20+
case 2 : // fifo depth
21+
o = 5;
22+
break;;
23+
}
24+
printf("get_p(%d): %d\n", w, (int)o);
25+
return o;
26+
}
27+
28+
void write_byte ( uint8_t id, uint32_t i, uint8_t v ) {
29+
V[id][i] = v;
30+
}
31+
32+
uint8_t read_byte ( uint8_t id, uint32_t i ) {
33+
return V[id][i];
34+
}
35+
36+
// check evaluates if the result produced by the UUT is equivalent to some other softwre procedure.
37+
int check(int32_t *I, int32_t *O, uint32_t l) {
38+
int i;
39+
for ( i=0 ; i<l ; i++ ) {
40+
if ( I[i] != O[i] ) {
41+
printf("check failed! %d: %d %d\n", i, I[i], O[i]);
42+
return -1;
43+
}
44+
}
45+
printf("check successful\n");
46+
return 0;
47+
}
48+
49+
// main is the entrypoint to the application.
50+
int main(int argc, char **argv) {
51+
52+
// Optionally, some of the CLI arguments can be processed by the software app and others forwarded to GHDL.
53+
int gargc = argc;
54+
char **gargv = argv;
55+
56+
// Allocate the memory buffers that are to be shared between the software and the simulation.
57+
int i;
58+
for (i=0 ; i<2 ; i++) {
59+
V[i] = (uint8_t *) malloc(length*sizeof(uint32_t));
60+
if ( V[i] == NULL ) {
61+
perror("execution of malloc() failed!\n");
62+
return -1;
63+
}
64+
}
65+
66+
// Initialize one of the buffers with random data.
67+
for (i=0 ; i<length ; i++) {
68+
((int32_t*)V[0])[i] = i*100+rand()/(RAND_MAX/100);
69+
printf("V[%d]: %d\n", i, ((int32_t*)V[0])[i]);
70+
}
71+
72+
// Execute the simulation to let the UUT copy the data from one buffer to another.
73+
ghdl_main(gargc, gargv);
74+
75+
// Check that the UUT did what it was expected to do.
76+
printf("> Call 'check'\n");
77+
if ( check((int32_t*)V[0], (int32_t*)V[1], length) != 0 ) {
78+
printf("check failed!\n");
79+
return -1;
80+
}
81+
82+
// Free the allocated memory, since we don't need it anymore.
83+
free(V[0]);
84+
free(V[1]);
85+
86+
return 0;
87+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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-2019, Lars Asplund lars.anders.asplund@gmail.com
6+
7+
library ieee;
8+
context ieee.ieee_std_context;
9+
10+
package pkg_c is
11+
12+
function get_param(f: integer) return integer;
13+
attribute foreign of get_param : function is "VHPIDIRECT get_param";
14+
15+
procedure write_byte ( id, i, v: integer ) ;
16+
attribute foreign of write_byte : procedure is "VHPIDIRECT write_byte";
17+
18+
impure function read_byte ( id, i: integer ) return integer;
19+
attribute foreign of read_byte : function is "VHPIDIRECT read_byte";
20+
21+
type memory_t is record
22+
-- Private
23+
p_id: integer;
24+
end record;
25+
26+
impure function new_memory(id: integer := -1) return memory_t;
27+
procedure write_word ( memory: memory_t; i: natural; w: std_logic_vector );
28+
impure function read_word ( memory: memory_t; i, bytes_per_word: integer ) return std_logic_vector;
29+
30+
procedure write_byte ( memory: memory_t; i: integer; v: std_logic_vector(7 downto 0) );
31+
impure function read_byte ( memory: memory_t; i: integer ) return std_logic_vector;
32+
33+
end pkg_c;
34+
35+
package body pkg_c is
36+
37+
-- VHPI
38+
39+
function get_param(f: integer) return integer is begin
40+
assert false report "VHPI" severity failure;
41+
end function;
42+
43+
procedure write_byte ( id, i, v: integer ) is begin
44+
assert false report "VHPI" severity failure;
45+
end procedure;
46+
47+
impure function read_byte ( id, i: integer ) return integer is begin
48+
assert false report "VHPI" severity failure;
49+
end function;
50+
51+
-- VHDL
52+
53+
procedure write_byte ( memory: memory_t; i: integer; v: std_logic_vector(7 downto 0) ) is
54+
begin
55+
write_byte(memory.p_id, i, to_integer(unsigned(v)));
56+
end procedure;
57+
58+
impure function read_byte ( memory: memory_t; i: integer ) return std_logic_vector is begin
59+
return std_logic_vector(to_unsigned(read_byte(memory.p_id, i), 8));
60+
end function;
61+
62+
impure function new_memory(id: integer := -1) return memory_t is begin
63+
return (p_id => id);
64+
end;
65+
66+
procedure write_word ( memory: memory_t; i: natural; w: std_logic_vector ) is begin
67+
for idx in 0 to w'length/8-1 loop
68+
write_byte(memory, i + idx, w(8*idx+7 downto 8*idx));
69+
end loop;
70+
end procedure;
71+
72+
impure function read_word ( memory: memory_t; i, bytes_per_word: integer ) return std_logic_vector is
73+
variable tmp: std_logic_vector(31 downto 0);
74+
begin
75+
for idx in 0 to bytes_per_word-1 loop
76+
tmp(8*idx+7 downto 8*idx) := read_byte(memory, i + idx);
77+
end loop;
78+
return tmp;
79+
end function;
80+
81+
end pkg_c;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include <stdio.h>
2+
3+
extern int ghdl_main (int argc, char **argv);
4+
5+
// main is the entrypoint to the application.
6+
int main(int argc, char **argv) {
7+
8+
printf("This is an example of how to wrap a GHDL + VUnit simulation in a C application.\n");
9+
10+
ghdl_main(argc, argv);
11+
12+
return 0;
13+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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-2019, Lars Asplund lars.anders.asplund@gmail.com
6+
7+
-- This testbench is a Minimum Working Example (MWE) of VUnit's resources to read/write data from a buffer
8+
-- allocated in a foreign C application, and to verify AXI4-Stream components. Data is sent to an AXI4-Stream
9+
-- Slave. The AXI4-Stream Slave is expected to be connected to an AXI4-Stream Master either directly or
10+
-- (preferredly) through a FIFO, thus composing a loopback. Therefore, as data is pushed to the AXI4-Stream
11+
-- Slave interface, the output is read from the AXI4-Stream Master interface and it is saved back to the buffer
12+
-- shared with the software application.
13+
14+
library ieee;
15+
context ieee.ieee_std_context;
16+
17+
library vunit_lib;
18+
context vunit_lib.vunit_context;
19+
context vunit_lib.vc_context;
20+
21+
use work.pkg_c;
22+
use work.pkg_c.all;
23+
24+
entity tb_c_axis_loop is
25+
generic (
26+
runner_cfg : string;
27+
tb_path : string
28+
);
29+
end entity;
30+
31+
architecture tb of tb_c_axis_loop is
32+
-- Simulation constants
33+
34+
constant clk_period : time := 20 ns;
35+
constant stream_length : integer := get_param(0);
36+
constant data_width : natural := get_param(1);
37+
constant fifo_depth : natural := get_param(2);
38+
39+
-- AXI4Stream Verification Components
40+
41+
constant m_axis : axi_stream_master_t := new_axi_stream_master(data_length => data_width);
42+
constant s_axis : axi_stream_slave_t := new_axi_stream_slave(data_length => data_width);
43+
44+
constant ibuffer: pkg_c.memory_t := pkg_c.new_memory(0);
45+
constant obuffer: pkg_c.memory_t := pkg_c.new_memory(1);
46+
47+
-- tb signals and variables
48+
49+
signal clk, rst, rstn : std_logic := '0';
50+
signal start, sent, saved : boolean := false;
51+
52+
begin
53+
54+
clk <= not clk after (clk_period/2);
55+
rstn <= not rst;
56+
57+
main: process
58+
procedure run_test is begin
59+
info("Init test");
60+
wait until rising_edge(clk); start <= true;
61+
wait until rising_edge(clk); start <= false;
62+
wait until (sent and saved and rising_edge(clk));
63+
info("Test done");
64+
end procedure;
65+
begin
66+
test_runner_setup(runner, runner_cfg);
67+
while test_suite loop
68+
if run("test") then
69+
rst <= '1';
70+
wait for 15*clk_period;
71+
rst <= '0';
72+
run_test;
73+
end if;
74+
end loop;
75+
test_runner_cleanup(runner);
76+
wait;
77+
end process;
78+
79+
--
80+
81+
stimuli: process
82+
variable last : std_logic;
83+
begin
84+
sent <= false;
85+
wait until start and rising_edge(clk);
86+
87+
for y in 0 to stream_length-1 loop
88+
wait until rising_edge(clk);
89+
push_axi_stream(net, m_axis, pkg_c.read_word(ibuffer, 4*y, 4) , tlast => '0');
90+
end loop;
91+
92+
info("m_I sent!");
93+
94+
wait until rising_edge(clk);
95+
sent <= true;
96+
wait;
97+
end process;
98+
99+
save: process
100+
variable o : std_logic_vector(31 downto 0);
101+
variable last : std_logic:='0';
102+
begin
103+
saved <= false;
104+
wait until start and rising_edge(clk);
105+
wait for 50*clk_period;
106+
107+
for y in 0 to stream_length-1 loop
108+
pop_axi_stream(net, s_axis, tdata => o, tlast => last);
109+
pkg_c.write_word(obuffer, 4*y, o);
110+
end loop;
111+
112+
info("m_O read!");
113+
114+
wait until rising_edge(clk);
115+
saved <= true;
116+
wait;
117+
end process;
118+
119+
--
120+
121+
uut_vc: entity work.tb_vc_axis_loop
122+
generic map (
123+
m_axis => m_axis,
124+
s_axis => s_axis,
125+
data_width => data_width,
126+
fifo_depth => fifo_depth
127+
)
128+
port map (
129+
clk => clk,
130+
rstn => rstn
131+
);
132+
133+
end architecture;

0 commit comments

Comments
 (0)