Skip to content

Commit

Permalink
vga/test: add co-simulation with tkinter window
Browse files Browse the repository at this point in the history
  • Loading branch information
umarcor committed Oct 20, 2020
1 parent c8cc672 commit c06230c
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 8 deletions.
20 changes: 16 additions & 4 deletions vga/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,27 @@ Sources in [test/hdl/](test/hdl) provide a *Virtual VGA screen* based on the [VG
<img src="./test/tb_vga.png"/>
</p>

### Imagemagick
### Imagemagick (animated GIF)

[test/hdl/imagemagick/](test/hdl/imagemagick) provides a backend for the virtual screen based on [Imagemagick](https://www.imagemagick.org/). `save_screenshot` saves each frame to a binary file in RGB24 format. Then, `convert` from Imagemagick is used for generating a PNG screenshot. In `sim_cleanup`, `convert` is used for merging all the PNGs into an animated GIF. Execute the run script for running the simulation:
[test/imagemagick/](test/imagemagick) provides a backend for the virtual screen based on [Imagemagick](https://www.imagemagick.org/). `save_screenshot` saves each frame to a binary file in RGB24 format. Then, `convert` from Imagemagick is used for generating a PNG screenshot. In `sim_cleanup`, `convert` is used for merging all the PNGs into an animated GIF. Execute the run script for running the simulation:

```sh
./test/hdl/imagemagick/run.sh
./test/imagemagick/run.sh
```

Images are saved to `test/hdl/imagemagick/out/`.
Images are saved to `test/imagemagick/out/`.

### Tkinter (desktop window)

[test/tkinter/](test/tkinter) provides a backend for the virtual screen based on [tkinter](https://docs.python.org/3/library/tkinter.html), the built-in Python interface to Tcl/Tk. The Tk GUI toolkit is available on most Unix platforms, as well as on Windows systems. [NumPy](https://numpy.org/)'s [ctypeslib](https://numpy.org/doc/stable/reference/routines.ctypeslib.html) and [Pillow](https://python-pillow.org/)'s [ImageTk](https://pillow.readthedocs.io/en/stable/reference/ImageTk.html) are used for transforming the VHDL buffer to an image and for displaying the frames in a desktop window. After installing the dependencies, execute the run script for running the simulation:

```sh
./test/tkinter/run.sh
```

A windows is shown on the desktop and it is updated after each frame is captured by the VHDL VGA monitor.

> NOTE: On MSYS2's MINGW64, `numpy` needs to be installed through `pacman`. Furthermore, installing `Pillow` through `pip` requires the packages listed in [pillow.rtfd.io: Building on Windows using MSYS2/MinGW](https://pillow.readthedocs.io/en/stable/installation.html#building-on-windows-using-msys2-mingw).
## Development

Expand Down
8 changes: 4 additions & 4 deletions vga/test/hdl/VGA_screen_pkg.vhd
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ package body VGA_screen_pkg is
variable raw24: std_logic_vector(31 downto 0);
begin
raw24 := (
7 downto 0 => rgb(0),
7 downto 0 => rgb(2),
15 downto 8 => rgb(1),
23 downto 16 => rgb(2),
others => '0'
23 downto 16 => rgb(0),
others => '1'
);
return to_integer(unsigned(raw24));
return to_integer(signed(raw24));
end function;

end VGA_screen_pkg;
33 changes: 33 additions & 0 deletions vga/test/tkinter/caux.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

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

void (*save_screenshot_cb)(int32_t*, uint32_t, uint32_t, int);
void save_screenshot(int32_t *ptr, uint32_t width, uint32_t height, int id) {
save_screenshot_cb(ptr, width, height, id);
}

void (*sim_cleanup_cb)(void);
void sim_cleanup(void) {
sim_cleanup_cb();
}

int py_main(
int argc,
void** argv,
void (*fptr_save_screenshot)(int32_t*, uint32_t, uint32_t, int),
void (*fptr_sim_cleanup)(void)
) {
printf("fptr_save_screenshot is %p\n", (void*)fptr_save_screenshot);
assert(fptr_save_screenshot != NULL);
save_screenshot_cb = fptr_save_screenshot;

printf("fptr_sim_cleanup is %p\n", (void*)fptr_sim_cleanup);
assert(fptr_sim_cleanup != NULL);
sim_cleanup_cb = fptr_sim_cleanup;

return ghdl_main(argc, argv);
}
6 changes: 6 additions & 0 deletions vga/test/tkinter/py.ver
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
VHPIDIRECT {
global:
py_main;
local:
*;
};
2 changes: 2 additions & 0 deletions vga/test/tkinter/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
numpy
Pillow
63 changes: 63 additions & 0 deletions vga/test/tkinter/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from pathlib import Path
import ctypes

from io import BytesIO

import numpy as np
from PIL import Image, ImageTk
from tkinter import Tk, Label

from utils import dlopen, dlclose, enc_args, FUNCTYPE


root = Tk()
root.title("[DBHI/vboard] VGA screen")
panel = Label(root, bd=0)
panel.pack()


def save_screenshot(img, width, height, id):
print(" Python save_screenshot:", img, width, height, id)
image = Image.fromarray(np.ctypeslib.as_array(img, shape=(height, width)))
image.mode = 'RGBA'

# TODO: passing 'image' to panel.image should be possible without writting to an intermediate buffer
buf = BytesIO()
image.save(buf, format="PNG")

pimg = ImageTk.PhotoImage(Image.open(buf))
panel.configure(image=pimg)
panel.image = pimg
root.update()


def sim_cleanup():
print(" Python sim_cleanup!")


def run_sim():
C_SAVE_SCREENSHOT = FUNCTYPE(
None,
ctypes.POINTER(ctypes.c_int),
ctypes.c_uint,
ctypes.c_uint,
ctypes.c_int
)(save_screenshot)

C_SIM_CLEANUP = FUNCTYPE(
None
)(sim_cleanup)

# TODO pass argv to GHDL
C_ARGS = enc_args([str(Path(__file__))])

CAUXDLL = dlopen("./caux.so")

print("> pymain")
print(CAUXDLL.py_main(len(C_ARGS), C_ARGS, C_SAVE_SCREENSHOT, C_SIM_CLEANUP))

dlclose(CAUXDLL)


root.after(0, run_sim)
root.mainloop()
42 changes: 42 additions & 0 deletions vga/test/tkinter/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env sh

set -e

cd $(dirname "$0")

PY="python3"
if ! command -v "$PY"; then
PY="python"
fi

echo "> Analyze ../src/*.vhd and ./hdl/*.vhd"
ghdl -a --std=08 -frelaxed ../../src/VGA_config_pkg.vhd
ghdl -a --std=08 -frelaxed ../../src/VGA_sync_gen_idx.vhd
ghdl -a --std=08 -frelaxed ../../src/VGA_sync_gen.vhd
ghdl -a --std=08 -frelaxed ../../src/VGA_sync_gen_cfg.vhd
ghdl -a --std=08 -frelaxed ../../src/Design_Top.vhd
ghdl -a --std=08 -frelaxed ../../src/demo.vhd

ghdl -a --std=08 -frelaxed ../hdl/VGA_screen_pkg.vhd
ghdl -a --std=08 -frelaxed ../hdl/VGA_screen.vhd
ghdl -a --std=08 -frelaxed ../hdl/VGA_tb.vhd

echo "> Build caux.so"
ghdl -e \
--std=08 \
-frelaxed \
-Wl,-fPIC \
-Wl,caux.c \
-Wl,-shared \
-Wl,-Wl,--version-script=./py.ver \
-Wl,-Wl,-u,ghdl_main \
-o caux.so \
tb_vga

rm *.o *.cf

#echo "> Execute tb (save wave.ghw)"
#./tb --wave=wave.ghw

echo "> Execute run.py"
$PY run.py --wave=wave.ghw
45 changes: 45 additions & 0 deletions vga/test/tkinter/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import sys
from sys import platform
from pathlib import Path
import ctypes
import _ctypes # type: ignore


FUNCTYPE = ctypes.WINFUNCTYPE if platform == 'win32' else ctypes.CFUNCTYPE

def dlopen(path):
"""
Open/load a PIE binary or a shared library.
"""
if not Path(path).is_file():
print("Executable binary not found: " + path)
sys.exit(1)
try:
return ctypes.CDLL(path)
except OSError:
print(
"Loading executables dynamically seems not to be supported on this platform"
)
sys.exit(1)


def dlclose(obj):
"""
Close/unload a PIE binary or a shared library.
:param obj: object returned by ctypes.CDLL when the resource was loaded
"""
if platform == "win32":
_ctypes.FreeLibrary(obj._handle) # pylint:disable=protected-access,no-member
else:
_ctypes.dlclose(obj._handle) # pylint:disable=protected-access,no-member


def enc_args(args):
"""
Convert args to a suitable format for a foreign C function.
:param args: list of strings
"""
C_ARGS = (ctypes.POINTER(ctypes.c_char) * len(args))()
for idx, arg in enumerate(args):
C_ARGS[idx] = ctypes.create_string_buffer(arg.encode("utf-8"))
return C_ARGS

0 comments on commit c06230c

Please sign in to comment.