# Vector/Pipelining scenario #1: RGB to YUV conversion¶

Example RGB to YUV conversion (simplified, not clamping)

Note Not verified for correct conversion, concept study only

Makes use of the VectorSig data type and the simple @pipeline decorator.

In [1]:
!pip install numpy > /dev/null

WARNING: You are using pip version 21.3; however, version 21.3.1 is available.
You should consider upgrading via the '/usr/local/bin/python -m pip install --upgrade pip' command.


Import video types:

In [2]:
from video.color import *
from video.videotypes import *


Import pipeline and target auxiliaries:

In [3]:
from myirl.library.pipeline import *
from myirl import targets


Construct the conversion matrix, in this case for JPEG-compliant YCrCb:

In [4]:
CLAMP = False
LEVELSHIFT = False
BPP        = 8
FRACT_SIZE = 16
CALCSIZE   = FRACT_SIZE + BPP
SATURATION_VALUE_MAX = 127 # YUV maximum value (saturation)
SATURATION_VALUE_MIN = -128 # YUV minimum value (saturation)

# Signed matrix entries:
Y_FROM_RGB = vector_to_fp(FRACT_SIZE, 1, mat_jpeg_rgb2yuv[0])
U_FROM_RGB = vector_to_fp(FRACT_SIZE, 1, mat_jpeg_rgb2yuv[1])
V_FROM_RGB = vector_to_fp(FRACT_SIZE, 1, mat_jpeg_rgb2yuv[2])

def F(x, s = FRACT_SIZE):
return intbv(x)[s:]

YUV_SLICE = slice(CALCSIZE-1, CALCSIZE-1 - BPP)

MATRIX = [
[ F(Y_FROM_RGB[i]) for i in range(3) ],
[ F(U_FROM_RGB[i]) for i in range(3) ],
[ F(V_FROM_RGB[i]) for i in range(3) ]
]

from myirl.vector import VectorSignal

I = lambda x: ( x[i]._val for i in range(3) )

# @bulkwrapper()
# class RGBParam:
#     def __init__(self):
#         self.y = VectorSig(3, MATRIX[0], initializer = I(MATRIX[0]))
#         self.u = VectorSig(3, MATRIX[1], initializer = I(MATRIX[1]))
#         self.v = VectorSig(3, MATRIX[1], initializer = I(MATRIX[2]))

In [5]:
MATRIX

Out[5]:
[[intbv(9797), intbv(19234), intbv(3735)],
[intbv(60007), intbv(54682), intbv(16384)],
[intbv(16384), intbv(51817), intbv(62872)]]
In [6]:
from myirl import simulation as sim
from myirl.test.common_test import gen_osc

@block
def video_rgb_yuv(clk : ClkSignal,
vin : VideoPort,
rgb : Signal,
param_matrix : list,
vout : VideoPort.Output,
yuv : Signal.Output,):
"""RGB to full range YUV422 converter, manual pipeline inference"""

py, pu, pv = [
VectorSignal(3, F(0), initializer = I(param_matrix[i]), name = "p_coef%d" % i)  \
for i in range(3)
]

# Use initializers:
py._init = True
pu._init = True
pv._init = True

valid = Signal(bool())

rgb_v = VectorSignal(3, FractUnsigned(0, BPP), name = 'rgbv')

a = VectorSignal(3, FractSigned(0, CALCSIZE+2), name = "add_res")
y = VectorSignal(3, FractUnsigned(0, CALCSIZE), name = "ydata")
u, v = [ VectorSignal(3, FractSigned(0, CALCSIZE+1), name = n) for n in ['udata', 'vdata'] ]

# Wire up input RGB video:
wires = []
for i in range(3):
j = 3 - i
wires.append(rgb_v[i].wireup(rgb[j*BPP:(j-1)*BPP]))

# Predefine YUV slices
yuv_slices = (a[i][YUV_SLICE] for i in range(3) )

wires += [
yuv.wireup(
concat(*yuv_slices)
)
]

@pipeline(clk, None, ce = vin.dval, pass_in = vin, pass_out = vout)
def yuv_pipe(ctx):
"""This contains the two-stage transformation for the RGB-YUV matrix.
Because it's a vector signal, we can use HDL notation (<=)"""
yield [
y <= (py * rgb_v),
u <= (pu.signed() * rgb_v),
v <= (pv.signed() * rgb_v)
]

# Create sum expressions for readability:
_y, _u, _v = (i.sum() for i in [y, u, v])

yield [
a[0].set(_y.signed()),
a[1].set(_u),
a[2].set(_v)
]

return locals()


### Testbench¶

In [7]:
from myirl.targets import VHDL
from myirl.test.common_test import run_ghdl

d = DesignModule("top", debug = True)

@component(d)
def testbench_rgb2yuv():
clk = ClkSignal(name = "pclk")
yuv = Signal(intbv(0)[3*BPP:])
vint, vout = [VideoPort() for _ in range(2)]

yuv = Signal(intbv(0)[3*BPP:], name = 'yuv_data')
rgb = Signal(intbv(0)[3*BPP:], name = 'rgb_data')

inst = video_rgb_yuv(clk = clk,
vin = vint,
rgb = rgb,
param_matrix = MATRIX,
vout = vout,
yuv = yuv
)

osc = gen_osc(clk, CYCLE = 5)

@sim.generator
def stimulus():

# Feed a few color values:
values = sim.Iterator([0x00ffff, 0x7f7f7f, 0x008300, 0x1a840a])

yield [
vint.dval.set(False), vint.fval.set(True), vint.lval.set(True),
sim.wait(4 * [ clk.posedge, ] ),
vint.dval.set(True),
sim.For(values)(
sim.wait('1 ns'),
rgb.set(values),
sim.wait(2 * [clk.posedge]),
sim.print_(yuv),
),

sim.wait(3 * [ clk.posedge, ] ),
sim.assert_(vout.dval == True, "Video not valid"),
]

for _ in range(3):
yield [
sim.print_(yuv),
sim.wait(clk.posedge),
]

yield [
sim.raise_(sim.StopSimulation)
]

return locals()

def test():
tb = testbench_rgb2yuv()
files = tb.elab(VHDL, elab_all = True)
run_ghdl(files, tb, debug = True, vcdfile="yuv.vcd")
return files, tb

 Declare obj 'testbench_rgb2yuv' in context 'top'

In [8]:
files, tb = test()

VHDL target: REGISTERING VideoPort <class 'myirl.library.bulksignals.VideoPort'>

/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/library/pipeline.py:149: TranslationWarning: yuv_pipe(): ce (type <class 'myirl.kernel.components.ChildAlias'>) is not a pipeline signal
base.warn("%s(): ce (type %s) is not a pipeline signal" % (func.__name__, type(ce)))
/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/library/pipeline.py:179: TranslationWarning: /tmp/ipykernel_133741/1859329269.py::video_rgb_yuv:47: p_coef0 (type <class 'myirl.library.style_hdl.HDLVectorSig'>) is not a pipeline signal
base.warn("%s: %s (type %s) is not a pipeline signal" % (self.trace_info(), n, type(src)))
/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/library/pipeline.py:179: TranslationWarning: /tmp/ipykernel_133741/1859329269.py::video_rgb_yuv:47: rgbv (type <class 'myirl.library.style_hdl.HDLVectorSig'>) is not a pipeline signal
base.warn("%s: %s (type %s) is not a pipeline signal" % (self.trace_info(), n, type(src)))
/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/library/pipeline.py:179: TranslationWarning: /tmp/ipykernel_133741/1859329269.py::video_rgb_yuv:47: p_coef1 (type <class 'myirl.library.style_hdl.HDLVectorSig'>) is not a pipeline signal
base.warn("%s: %s (type %s) is not a pipeline signal" % (self.trace_info(), n, type(src)))
/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/library/pipeline.py:179: TranslationWarning: /tmp/ipykernel_133741/1859329269.py::video_rgb_yuv:47: p_coef2 (type <class 'myirl.library.style_hdl.HDLVectorSig'>) is not a pipeline signal
base.warn("%s: %s (type %s) is not a pipeline signal" % (self.trace_info(), n, type(src)))

 Insert unit video_rgb_yuv_s1dval_1_slval_1_sfval_1_s24_l3dval_1_slval_1_sfval_1_s24
Creating sequential 'testbench_rgb2yuv/stimulus'
Insert unit testbench_rgb2yuv
DEBUG Skip latency accounting for ydata
DEBUG Skip latency accounting for udata
DEBUG Skip latency accounting for vdata

/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/library/pipeline.py:179: TranslationWarning: /tmp/ipykernel_133741/1859329269.py::video_rgb_yuv:47: ydata (type <class 'myirl.lists.SigArrayElem'>) is not a pipeline signal
base.warn("%s: %s (type %s) is not a pipeline signal" % (self.trace_info(), n, type(src)))
/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/library/pipeline.py:179: TranslationWarning: /tmp/ipykernel_133741/1859329269.py::video_rgb_yuv:47: udata (type <class 'myirl.lists.SigArrayElem'>) is not a pipeline signal
base.warn("%s: %s (type %s) is not a pipeline signal" % (self.trace_info(), n, type(src)))
/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/library/pipeline.py:179: TranslationWarning: /tmp/ipykernel_133741/1859329269.py::video_rgb_yuv:47: vdata (type <class 'myirl.lists.SigArrayElem'>) is not a pipeline signal
base.warn("%s: %s (type %s) is not a pipeline signal" % (self.trace_info(), n, type(src)))
/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/kernel/components.py:980: TranslationWarning: @component video_rgb_yuv: DEBUG UNUSED 'vin'
base.warn("@component %s: DEBUG UNUSED '%s'" % (self.obj.func.__name__, n), category = base.TranslationWarning)
/home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/kernel/components.py:980: TranslationWarning: @component video_rgb_yuv: DEBUG UNUSED 'valid'
base.warn("@component %s: DEBUG UNUSED '%s'" % (self.obj.func.__name__, n), category = base.TranslationWarning)

DEBUG Skip latency accounting for add_res
Writing 'video_rgb_yuv' to file /tmp/video_rgb_yuv.vhdl
Finished _elab in 0.0026 secs
Writing 'testbench_rgb2yuv' to file /tmp/testbench_rgb2yuv.vhdl
Finished _elab in 0.1370 secs
Creating library file /tmp/module_defs.vhdl
==== COSIM stdout ====

==== COSIM stderr ====

==== COSIM stdout ====
analyze /home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/targets/../test/vhdl/txt_util.vhdl
analyze /home/testing/.local/lib/python3.10/site-packages/myirl-0.0.0-py3.10-linux-x86_64.egg/myirl/targets/libmyirl.vhdl
analyze /tmp/module_defs.vhdl
analyze /tmp/video_rgb_yuv.vhdl
analyze /tmp/testbench_rgb2yuv.vhdl
elaborate testbench_rgb2yuv

==== COSIM stderr ====

==== COSIM stdout ====
0xuuuuuu
0xB22B80
0x7E0000
0x4CD4C9
0x56D4D4
0x56D4D4
0x56D4D4
/tmp/testbench_rgb2yuv.vhdl:90:9:@175ns:(assertion failure): Stop Simulation
/tmp/testbench_rgb2yuv:error: assertion failed
in process .testbench_rgb2yuv(myirl).stimulus
/tmp/testbench_rgb2yuv:error: simulation failed

==== COSIM stderr ====



## Waveform trace¶

In [9]:
import wavedraw; import nbwavedrom
TB = tb.name;

waveform = wavedraw.vcd2wave("yuv.vcd", TB + '.pclk', None)
nbwavedrom.draw(waveform)


In [10]:
!cat -n {files[0]}

     1	-- File generated from /usr/local/lib/python3.10/runpy.py
2	-- (c) 2016-2021 section5.ch
3	-- Modifications may be lost
4
5	library IEEE;
6	use IEEE.std_logic_1164.all;
7	use IEEE.numeric_std.all;
8
9	library work;
10
11	use work.module_defs.all;
12	use work.txt_util.all;
13	use work.myirl_conversion.all;
14
15
16
17	entity video_rgb_yuv is
18	    port (
19	        clk : in std_ulogic;
20	        vin : in t_VideoPort;
21	        rgb : in unsigned(23 downto 0);
22	        vout : out t_VideoPort;
23	        yuv : out unsigned(23 downto 0)
24	    );
25	end entity video_rgb_yuv;
26
27	architecture MyIRL of video_rgb_yuv is
28	    -- Local type declarations
29	    -- Signal declarations
30	    signal yuv_pipe_ce1 : std_ulogic;
31	    signal yuv_pipe_ce2 : std_ulogic;
32	    signal yuv_pipe_ce0 : std_ulogic;
33	    type a_ydata is array (0 to 2) of unsigned(23 downto 0);
34	    signal ydata : a_ydata    ;
35	    type a_udata is array (0 to 2) of signed(24 downto 0);
36	    signal udata : a_udata    ;
37	    type a_vdata is array (0 to 2) of signed(24 downto 0);
38	    signal vdata : a_vdata    ;
39	    type a_add_res is array (0 to 2) of signed(25 downto 0);
41	    signal yuv_pipe_bypass1 : t_VideoPort;
42	    signal yuv_pipe_bypass2 : t_VideoPort;
43	    signal yuv_pipe_bypass0 : t_VideoPort;
44	    type a_p_coef0 is array (0 to 2) of unsigned(15 downto 0);
45	    signal p_coef0 : a_p_coef0     := (
46	        x"2645",x"4b22",x"0e97"
47	    );
48	    type a_rgbv is array (0 to 2) of unsigned(7 downto 0);
49	    signal rgbv : a_rgbv    ;
50	    type a_p_coef1 is array (0 to 2) of unsigned(15 downto 0);
51	    signal p_coef1 : a_p_coef1     := (
52	        x"ea67",x"d59a",x"4000"
53	    );
54	    type a_p_coef2 is array (0 to 2) of unsigned(15 downto 0);
55	    signal p_coef2 : a_p_coef2     := (
56	        x"4000",x"ca69",x"f598"
57	    );
58	begin
59
60	ce_queue:
61	    process(clk)
62	    begin
63	        if rising_edge(clk) then
64	            yuv_pipe_ce1 <= yuv_pipe_ce0;
65	            yuv_pipe_ce2 <= yuv_pipe_ce1;
66	        end if;
67	    end process;
68	    yuv_pipe_ce0 <= vin.dval;
69
70	yuv_pipe_stage0:
71	    process(clk)
72	    begin
73	        if rising_edge(clk) then
74	            if (yuv_pipe_ce0 = '1') then
75	                ydata(0) <= (p_coef0(0) * rgbv(0));
76	                ydata(1) <= (p_coef0(1) * rgbv(1));
77	                ydata(2) <= (p_coef0(2) * rgbv(2));
78	                udata(0) <= resize(((signed(p_coef1(0)) * signed(resize((rgbv(0)), 9)))), 25);
79	                udata(1) <= resize(((signed(p_coef1(1)) * signed(resize((rgbv(1)), 9)))), 25);
80	                udata(2) <= resize(((signed(p_coef1(2)) * signed(resize((rgbv(2)), 9)))), 25);
81	                vdata(0) <= resize(((signed(p_coef2(0)) * signed(resize((rgbv(0)), 9)))), 25);
82	                vdata(1) <= resize(((signed(p_coef2(1)) * signed(resize((rgbv(1)), 9)))), 25);
83	                vdata(2) <= resize(((signed(p_coef2(2)) * signed(resize((rgbv(2)), 9)))), 25);
84	            end if;
85	        end if;
86	    end process;
87
88	yuv_pipe_stage1:
89	    process(clk)
90	    begin
91	        if rising_edge(clk) then
92	            if (yuv_pipe_ce1 = '1') then
93	                add_res(0) <= signed((resize(((ydata(0) + resize((ydata(1)), 25))), 26) + ydata(2)));
94	                add_res(1) <= resize(((resize(((udata(0) + resize((udata(1)), 26))), 27) + udata(2))), 26);
95	                add_res(2) <= resize(((resize(((vdata(0) + resize((vdata(1)), 26))), 27) + vdata(2))), 26);
96	            end if;
97	        end if;
98	    end process;
99
100	delay_queue:
101	    process(clk)
102	    begin
103	        if rising_edge(clk) then
104	            yuv_pipe_bypass1 <= yuv_pipe_bypass0;
105	            yuv_pipe_bypass2 <= yuv_pipe_bypass1;
106	        end if;
107	    end process;
108	    vout <= yuv_pipe_bypass2;
109	    yuv_pipe_bypass0 <= vin;
110	    rgbv(0) <= rgb(24-1 downto 16);
111	    rgbv(1) <= rgb(16-1 downto 8);
112	    rgbv(2) <= rgb(8-1 downto 0);
114	end architecture MyIRL;
115


## Verification exercise¶

Using numpy, we can run our samples through the floating point matrix as well:

In [11]:
v = numpy.matrix(mat_jpeg_rgb2yuv)
rgb = numpy.matrix([ (127, 127, 127), (0, 255, 255), (0, 0x83, 0)]).T

yuv = v * rgb

In [12]:
g = lambda x: "%02x" % (int(x) & 0xff)
f = numpy.vectorize(g)
f(yuv.T)

Out[12]:
matrix([['7e', '00', '00'],
['b2', '2b', '81'],
['4c', 'd5', 'ca']], dtype='<U2')

We note that the results don't entirely match. Why?