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 
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 ====

Waveform trace

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

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

Download VCD trace yuv.vcd

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);
    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	

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?