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.
!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:
from video.color import *
from video.videotypes import *
Import pipeline and target auxiliaries:
from myirl.library.pipeline import *
from myirl import targets
Construct the conversion matrix, in this case for JPEG-compliant YCrCb:
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]))
MATRIX
[[intbv(9797), intbv(19234), intbv(3735)], [intbv(60007), intbv(54682), intbv(16384)], [intbv(16384), intbv(51817), intbv(62872)]]
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()
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'
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 Warning: Implicit truncation of ADD(ADD(udata, udata), udata) result Warning: Implicit truncation of ADD(ADD(vdata, vdata), vdata) result 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 ====
import wavedraw; import nbwavedrom
TB = tb.name;
waveform = wavedraw.vcd2wave("yuv.vcd", TB + '.pclk', None)
nbwavedrom.draw(waveform)
Download VCD trace yuv.vcd
!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); 40 signal add_res : a_add_res; 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); 113 yuv <= (unsigned(add_res(0)(23-1 downto 15)) & unsigned(add_res(1)(23-1 downto 15)) & unsigned(add_res(2)(23-1 downto 15))); 114 end architecture MyIRL; 115
Using numpy, we can run our samples through the floating point matrix as well:
v = numpy.matrix(mat_jpeg_rgb2yuv)
rgb = numpy.matrix([ (127, 127, 127), (0, 255, 255), (0, 0x83, 0)]).T
yuv = v * rgb
g = lambda x: "%02x" % (int(x) & 0xff)
f = numpy.vectorize(g)
f(yuv.T)
matrix([['7e', '00', '00'], ['b2', '2b', '81'], ['4c', 'd5', 'ca']], dtype='<U2')
We note that the results don't entirely match. Why?