Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PoC] Interfacing GHDL to other languages: declaration of foreign C functions #465

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion examples/vhdl/array_axis_vcs/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,36 @@
"""

from pathlib import Path
from os import popen
from vunit import VUnit

VU = VUnit.from_argv()
VU.add_verification_components()

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

VU.add_library("lib").add_source_files([SRC_PATH / "*.vhd", SRC_PATH / "**" / "*.vhd"])
LIB = vu.add_library("lib")
LIB.add_source_files([SRC_PATH / "*.vhd", SRC_PATH / "**" / "*.vhd"])

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

C_NOBJ = join(src_path, "test", "stubs.o")
C_OBJ = join(src_path, "test", "main.o")
print(
popen(
"gcc -fPIC -rdynamic -c " + join(src_path, "**", "stubs.c") + " -o " + C_NOBJ
).read()
)
print(
popen(
"gcc -fPIC -rdynamic -c " + join(src_path, "**", "main.c") + " -o " + C_OBJ
).read()
)

for tb in lib.get_test_benches(pattern="*tb_py_*", allow_empty=False):
tb.set_sim_option("ghdl.elab_flags", ["-Wl," + C_NOBJ], overwrite=False)

for tb in lib.get_test_benches(pattern="*tb_c_*", allow_empty=False):
tb.set_sim_option("ghdl.elab_flags", ["-Wl," + C_OBJ], overwrite=False)

VU.main()
87 changes: 87 additions & 0 deletions examples/vhdl/array_axis_vcs/src/test/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

extern int ghdl_main (int argc, char **argv);

uint8_t *V[2];
uint32_t length = 100;

// get_param is used by GHDL to retrieve parameter values (integers).
uint32_t get_param(uint32_t w) {
uint32_t o = 0;
switch(w) {
case 0 : // buffer length
o = length;
break;;
case 1 : // data width (in bits)
o = 8*sizeof(int32_t);
break;;
case 2 : // fifo depth
o = 5;
break;;
}
printf("get_p(%d): %d\n", w, (int)o);
return o;
}

void write_byte ( uint8_t id, uint32_t i, uint8_t v ) {
V[id][i] = v;
}

uint8_t read_byte ( uint8_t id, uint32_t i ) {
return V[id][i];
}

// check evaluates if the result produced by the UUT is equivalent to some other softwre procedure.
int check(int32_t *I, int32_t *O, uint32_t l) {
int i;
for ( i=0 ; i<l ; i++ ) {
if ( I[i] != O[i] ) {
printf("check failed! %d: %d %d\n", i, I[i], O[i]);
return -1;
}
}
printf("check successful\n");
return 0;
}

// main is the entrypoint to the application.
int main(int argc, char **argv) {

// Optionally, some of the CLI arguments can be processed by the software app and others forwarded to GHDL.
int gargc = argc;
char **gargv = argv;

// Allocate the memory buffers that are to be shared between the software and the simulation.
int i;
for (i=0 ; i<2 ; i++) {
V[i] = (uint8_t *) malloc(length*sizeof(uint32_t));
if ( V[i] == NULL ) {
perror("execution of malloc() failed!\n");
return -1;
}
}

// Initialize one of the buffers with random data.
for (i=0 ; i<length ; i++) {
((int32_t*)V[0])[i] = i*100+rand()/(RAND_MAX/100);
printf("V[%d]: %d\n", i, ((int32_t*)V[0])[i]);
}

// Execute the simulation to let the UUT copy the data from one buffer to another.
ghdl_main(gargc, gargv);

// Check that the UUT did what it was expected to do.
printf("> Call 'check'\n");
if ( check((int32_t*)V[0], (int32_t*)V[1], length) != 0 ) {
printf("check failed!\n");
return -1;
}

// Free the allocated memory, since we don't need it anymore.
free(V[0]);
free(V[1]);

return 0;
}
81 changes: 81 additions & 0 deletions examples/vhdl/array_axis_vcs/src/test/pkg_c.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this file,
-- You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright (c) 2014-2019, Lars Asplund lars.anders.asplund@gmail.com

library ieee;
context ieee.ieee_std_context;

package pkg_c is

function get_param(f: integer) return integer;
attribute foreign of get_param : function is "VHPIDIRECT get_param";

procedure write_byte ( id, i, v: integer ) ;
attribute foreign of write_byte : procedure is "VHPIDIRECT write_byte";

impure function read_byte ( id, i: integer ) return integer;
attribute foreign of read_byte : function is "VHPIDIRECT read_byte";

type memory_t is record
-- Private
p_id: integer;
end record;

impure function new_memory(id: integer := -1) return memory_t;
procedure write_word ( memory: memory_t; i: natural; w: std_logic_vector );
impure function read_word ( memory: memory_t; i, bytes_per_word: integer ) return std_logic_vector;

procedure write_byte ( memory: memory_t; i: integer; v: std_logic_vector(7 downto 0) );
impure function read_byte ( memory: memory_t; i: integer ) return std_logic_vector;

end pkg_c;

package body pkg_c is

-- VHPI

function get_param(f: integer) return integer is begin
assert false report "VHPI" severity failure;
end function;

procedure write_byte ( id, i, v: integer ) is begin
assert false report "VHPI" severity failure;
end procedure;

impure function read_byte ( id, i: integer ) return integer is begin
assert false report "VHPI" severity failure;
end function;

-- VHDL

procedure write_byte ( memory: memory_t; i: integer; v: std_logic_vector(7 downto 0) ) is
begin
write_byte(memory.p_id, i, to_integer(unsigned(v)));
end procedure;

impure function read_byte ( memory: memory_t; i: integer ) return std_logic_vector is begin
return std_logic_vector(to_unsigned(read_byte(memory.p_id, i), 8));
end function;

impure function new_memory(id: integer := -1) return memory_t is begin
return (p_id => id);
end;

procedure write_word ( memory: memory_t; i: natural; w: std_logic_vector ) is begin
for idx in 0 to w'length/8-1 loop
write_byte(memory, i + idx, w(8*idx+7 downto 8*idx));
end loop;
end procedure;

impure function read_word ( memory: memory_t; i, bytes_per_word: integer ) return std_logic_vector is
variable tmp: std_logic_vector(31 downto 0);
begin
for idx in 0 to bytes_per_word-1 loop
tmp(8*idx+7 downto 8*idx) := read_byte(memory, i + idx);
end loop;
return tmp;
end function;

end pkg_c;
13 changes: 13 additions & 0 deletions examples/vhdl/array_axis_vcs/src/test/stubs.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include <stdio.h>

extern int ghdl_main (int argc, char **argv);

// main is the entrypoint to the application.
int main(int argc, char **argv) {

printf("This is an example of how to wrap a GHDL + VUnit simulation in a C application.\n");

ghdl_main(argc, argv);

return 0;
}
133 changes: 133 additions & 0 deletions examples/vhdl/array_axis_vcs/src/test/tb_c_axis_loop.vhd
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
-- This Source Code Form is subject to the terms of the Mozilla Public
-- License, v. 2.0. If a copy of the MPL was not distributed with this file,
-- You can obtain one at http://mozilla.org/MPL/2.0/.
--
-- Copyright (c) 2014-2019, Lars Asplund lars.anders.asplund@gmail.com

-- This testbench is a Minimum Working Example (MWE) of VUnit's resources to read/write data from a buffer
-- allocated in a foreign C application, and to verify AXI4-Stream components. Data is sent to an AXI4-Stream
-- Slave. The AXI4-Stream Slave is expected to be connected to an AXI4-Stream Master either directly or
-- (preferredly) through a FIFO, thus composing a loopback. Therefore, as data is pushed to the AXI4-Stream
-- Slave interface, the output is read from the AXI4-Stream Master interface and it is saved back to the buffer
-- shared with the software application.

library ieee;
context ieee.ieee_std_context;

library vunit_lib;
context vunit_lib.vunit_context;
context vunit_lib.vc_context;

use work.pkg_c;
use work.pkg_c.all;

entity tb_c_axis_loop is
generic (
runner_cfg : string;
tb_path : string
);
end entity;

architecture tb of tb_c_axis_loop is
-- Simulation constants

constant clk_period : time := 20 ns;
constant stream_length : integer := get_param(0);
constant data_width : natural := get_param(1);
constant fifo_depth : natural := get_param(2);

-- AXI4Stream Verification Components

constant m_axis : axi_stream_master_t := new_axi_stream_master(data_length => data_width);
constant s_axis : axi_stream_slave_t := new_axi_stream_slave(data_length => data_width);

constant ibuffer: pkg_c.memory_t := pkg_c.new_memory(0);
constant obuffer: pkg_c.memory_t := pkg_c.new_memory(1);

-- tb signals and variables

signal clk, rst, rstn : std_logic := '0';
signal start, sent, saved : boolean := false;

begin

clk <= not clk after (clk_period/2);
rstn <= not rst;

main: process
procedure run_test is begin
info("Init test");
wait until rising_edge(clk); start <= true;
wait until rising_edge(clk); start <= false;
wait until (sent and saved and rising_edge(clk));
info("Test done");
end procedure;
begin
test_runner_setup(runner, runner_cfg);
while test_suite loop
if run("test") then
rst <= '1';
wait for 15*clk_period;
rst <= '0';
run_test;
end if;
end loop;
test_runner_cleanup(runner);
wait;
end process;

--

stimuli: process
variable last : std_logic;
begin
sent <= false;
wait until start and rising_edge(clk);

for y in 0 to stream_length-1 loop
wait until rising_edge(clk);
push_axi_stream(net, m_axis, pkg_c.read_word(ibuffer, 4*y, 4) , tlast => '0');
end loop;

info("m_I sent!");

wait until rising_edge(clk);
sent <= true;
wait;
end process;

save: process
variable o : std_logic_vector(31 downto 0);
variable last : std_logic:='0';
begin
saved <= false;
wait until start and rising_edge(clk);
wait for 50*clk_period;

for y in 0 to stream_length-1 loop
pop_axi_stream(net, s_axis, tdata => o, tlast => last);
pkg_c.write_word(obuffer, 4*y, o);
end loop;

info("m_O read!");

wait until rising_edge(clk);
saved <= true;
wait;
end process;

--

uut_vc: entity work.tb_vc_axis_loop
generic map (
m_axis => m_axis,
s_axis => s_axis,
data_width => data_width,
fifo_depth => fifo_depth
)
port map (
clk => clk,
rstn => rstn
);

end architecture;
Loading