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
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 '(DesignModule 'top')'
files, tb = test()
DEBUG GET CONTEXT [Component 'video_rgb_yuv/video_rgb_yuv'] Adding param_matrix to port dict (fallback) Insert unit video_rgb_yuv_s1dval_1_slval_1_sfval_1_s24_l3dval_1_slval_1_sfval_1_s24 Insert unit testbench_rgb2yuv DEBUG Skip latency accounting for `ydata` DEBUG Skip latency accounting for `udata` DEBUG Skip latency accounting for `vdata` DEBUG Skip latency accounting for `add_res` DEBUG: Skip array for evaluation: 'ydata' DEBUG: Skip array for evaluation: 'udata' DEBUG: Skip array for evaluation: 'vdata' DEBUG: Skip array for evaluation: 'p_coef0' DEBUG: Skip array for evaluation: 'rgbv' DEBUG: Skip array for evaluation: 'p_coef1' DEBUG: Skip array for evaluation: 'p_coef2' DEBUG: Skip array for evaluation: 'ydata' DEBUG: Skip array for evaluation: 'udata' DEBUG: Skip array for evaluation: 'vdata' DEBUG: Skip array for evaluation: 'p_coef0' DEBUG: Skip array for evaluation: 'rgbv' DEBUG: Skip array for evaluation: 'p_coef1' DEBUG: Skip array for evaluation: 'p_coef2' DEBUG: Skip array for evaluation: 'ydata' DEBUG: Skip array for evaluation: 'udata' DEBUG: Skip array for evaluation: 'vdata' DEBUG: Skip array for evaluation: 'p_coef0' DEBUG: Skip array for evaluation: 'rgbv' DEBUG: Skip array for evaluation: 'p_coef1' DEBUG: Skip array for evaluation: 'p_coef2' DEBUG: Skip array for evaluation: 'ydata' DEBUG: Skip array for evaluation: 'udata' DEBUG: Skip array for evaluation: 'vdata' DEBUG: Skip array for evaluation: 'p_coef0' DEBUG: Skip array for evaluation: 'rgbv' DEBUG: Skip array for evaluation: 'p_coef1' DEBUG: Skip array for evaluation: 'p_coef2' DEBUG: Skip array for evaluation: 'ydata' DEBUG: Skip array for evaluation: 'udata' DEBUG: Skip array for evaluation: 'vdata' DEBUG: Skip array for evaluation: 'p_coef0' DEBUG: Skip array for evaluation: 'rgbv' DEBUG: Skip array for evaluation: 'p_coef1' DEBUG: Skip array for evaluation: 'p_coef2' 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
/home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/library/pipeline.py:147: TranslationWarning: yuv_pipe(): `ce` (type <class 'myirl.kernel._types.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.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/library/pipeline.py:177: TranslationWarning: /tmp/ipykernel_172075/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.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/library/pipeline.py:177: TranslationWarning: /tmp/ipykernel_172075/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.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/library/pipeline.py:177: TranslationWarning: /tmp/ipykernel_172075/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.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/library/pipeline.py:177: TranslationWarning: /tmp/ipykernel_172075/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))) /home/testing/.local/lib/python3.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/library/pipeline.py:177: TranslationWarning: /tmp/ipykernel_172075/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.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/library/pipeline.py:177: TranslationWarning: /tmp/ipykernel_172075/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.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/library/pipeline.py:177: TranslationWarning: /tmp/ipykernel_172075/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.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/kernel/instance.py:424: 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.9/site-packages/myirl-0.0.0-py3.9-linux-x86_64.egg/myirl/kernel/instance.py:424: TranslationWarning: @component `video_rgb_yuv`: DEBUG UNUSED 'valid' base.warn("@component `%s`: DEBUG UNUSED '%s'" % (self.obj.func.__name__, n), category = base.TranslationWarning)
Writing 'testbench_rgb2yuv' to file /tmp/testbench_rgb2yuv.vhdl Creating library file /tmp/module_defs.vhdl ==== COSIM stdout ==== 0xuuuuuu 0xB22B80 0x7E0000 0x4CD4C9 0x56D4D4 0x56D4D4 0x56D4D4 simulation stopped @175ns
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 source: 2 -- /tmp/ipykernel_172075/1859329269.py 3 -- (c) 2016-2022 section5.ch 4 -- Modifications may be lost, edit the source file instead. 5 6 library IEEE; 7 use IEEE.std_logic_1164.all; 8 use IEEE.numeric_std.all; 9 10 library work; 11 12 use work.module_defs.all; 13 use work.txt_util.all; 14 use work.myirl_conversion.all; 15 16 17 18 entity video_rgb_yuv is 19 port ( 20 clk : in std_ulogic; 21 vin : in t_VideoPort; 22 rgb : in unsigned(23 downto 0); 23 vout : out t_VideoPort; 24 yuv : out unsigned(23 downto 0) 25 ); 26 end entity video_rgb_yuv; 27 28 architecture myIRL of video_rgb_yuv is 29 -- Local type declarations 30 -- Signal declarations 31 signal yuv_pipe_ce1 : std_ulogic; 32 signal yuv_pipe_ce2 : std_ulogic; 33 signal yuv_pipe_ce0 : std_ulogic; 34 type a_ydata is array (0 to 2) of unsigned(23 downto 0); 35 signal ydata : a_ydata ; 36 type a_udata is array (0 to 2) of signed(24 downto 0); 37 signal udata : a_udata ; 38 type a_vdata is array (0 to 2) of signed(24 downto 0); 39 signal vdata : a_vdata ; 40 type a_add_res is array (0 to 2) of signed(25 downto 0); 41 signal add_res : a_add_res; 42 signal yuv_pipe_bypass1 : t_VideoPort; 43 signal yuv_pipe_bypass2 : t_VideoPort; 44 signal yuv_pipe_bypass0 : t_VideoPort; 45 type a_p_coef0 is array (0 to 2) of unsigned(15 downto 0); 46 signal p_coef0 : a_p_coef0 := ( 47 x"2645",x"4b22",x"0e97" 48 ); 49 type a_rgbv is array (0 to 2) of unsigned(7 downto 0); 50 signal rgbv : a_rgbv ; 51 type a_p_coef1 is array (0 to 2) of unsigned(15 downto 0); 52 signal p_coef1 : a_p_coef1 := ( 53 x"ea67",x"d59a",x"4000" 54 ); 55 type a_p_coef2 is array (0 to 2) of unsigned(15 downto 0); 56 signal p_coef2 : a_p_coef2 := ( 57 x"4000",x"ca69",x"f598" 58 ); 59 begin 60 61 ce_queue: 62 process(clk) 63 begin 64 if rising_edge(clk) then 65 yuv_pipe_ce1 <= yuv_pipe_ce0; 66 yuv_pipe_ce2 <= yuv_pipe_ce1; 67 end if; 68 end process; 69 yuv_pipe_ce0 <= vin.dval; 70 71 yuv_pipe_stage0: 72 process(clk) 73 begin 74 if rising_edge(clk) then 75 if (yuv_pipe_ce0 = '1') then 76 ydata(0) <= (p_coef0(0) * rgbv(0)); 77 ydata(1) <= (p_coef0(1) * rgbv(1)); 78 ydata(2) <= (p_coef0(2) * rgbv(2)); 79 udata(0) <= signed(resize((signed(p_coef1(0)) * signed(resize(rgbv(0), 9))), 25)); 80 udata(1) <= signed(resize((signed(p_coef1(1)) * signed(resize(rgbv(1), 9))), 25)); 81 udata(2) <= signed(resize((signed(p_coef1(2)) * signed(resize(rgbv(2), 9))), 25)); 82 vdata(0) <= signed(resize((signed(p_coef2(0)) * signed(resize(rgbv(0), 9))), 25)); 83 vdata(1) <= signed(resize((signed(p_coef2(1)) * signed(resize(rgbv(1), 9))), 25)); 84 vdata(2) <= signed(resize((signed(p_coef2(2)) * signed(resize(rgbv(2), 9))), 25)); 85 end if; 86 end if; 87 end process; 88 89 yuv_pipe_stage1: 90 process(clk) 91 begin 92 if rising_edge(clk) then 93 if (yuv_pipe_ce1 = '1') then 94 add_res(0) <= signed((resize((ydata(0) + resize(ydata(1), 25)), 26) + ydata(2))); 95 add_res(1) <= signed(resize((signed(resize((udata(0) + signed(resize(udata(1), 26))), 27)) + udata(2)), 26)); 96 add_res(2) <= signed(resize((signed(resize((vdata(0) + signed(resize(vdata(1), 26))), 27)) + vdata(2)), 26)); 97 end if; 98 end if; 99 end process; 100 101 delay_queue: 102 process(clk) 103 begin 104 if rising_edge(clk) then 105 yuv_pipe_bypass1 <= yuv_pipe_bypass0; 106 yuv_pipe_bypass2 <= yuv_pipe_bypass1; 107 end if; 108 end process; 109 vout <= yuv_pipe_bypass2; 110 yuv_pipe_bypass0 <= vin; 111 rgbv(0) <= rgb(24-1 downto 16); 112 rgbv(1) <= rgb(16-1 downto 8); 113 rgbv(2) <= rgb(8-1 downto 0); 114 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))); 115 end architecture myIRL; 116
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?