Golborne Vintage Radio

Full Version: Building a Standards Converter
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
I'll cover this in more detail tomorrow but the main concept you need is a Numerically Controlled Oscillator: https://en.wikipedia.org/wiki/Numericall...oscillator Sometimes called a DTO (Discrete TIme Oscillator). This is definitely beyond beginner's level but isn't too hard to understand for the purposes we'll be using it for. You don't need to worry about spurii etc as shown in that wiki article. DTOs are amazingly useful in digital design and have a specific application in standards conversion.

An adder and register, used as an accumulator, can perform arbitrary frequency division by non-integer values. For N bits the division ratio is K/(2^N) where K is the coefficient that you're adding at each clock cycle. There's also a slightly more complex method of doing X/Y division where Y isn't a power of 2 but we don't need that here. Here's a clue. 405/625 can be more than adequately approximated with a simple DTO. You can get both the line number as an integer and the fractional part which tells you how near the output line is to an input line. That fractional part drives the interpolator. Incidentally, if you vary the coefficient that will adjust the height of the output picture - you've just made part of a zoom machineSmile

Tomorrow I'll post samples of code from my own 625 to 405 converter showing how all this works in practice.
Hi Jeffrey
That’s an interesting concept. When I read your second last post I was saying to myself, that the 405 output would have to be synchronous to the input to be able to do that. But reading your last post it sounds as if that will work asynchronously, which is very cleaver.

Frank
No need to be synchronous at all. Obviously if you have only line stores then the fields have to be coincident but with framestores the input and output can be completely async. My own experimental converter is based on a XC2S200 (now obsolete Spartan 2 series) where I have some boards left over from a professional project I did for a client. There's some SDRAM on the board which I use to make a framestore. This is a little bit complicated but the result is that video can be written to the SDRAM at the same time as 4 separate video streams can be read. This gives me up to 4 line interpolation if I wish. It's usual to do interpolation upstram of the memory for down conversion - with linestores it's essential - but since I've got framestores with full random access it's easier for me to interpolate downstream.

I've attached the central timing module from my design. Mainly to show how I used a DTO to make the 405 output clock. I took the 54MHz clock from an oscillator on the board, mutliplied up to 108MHz using the clock processing available on the Xilinx. Then used a DTO to get the required output frequency. The important bit starts at line 96. For convenience I make a 405 clock that's 4x the sample rate. This allows me to economise on logic by using 1 mutliplier to do an entire 4 line interpolator.

The observant among you will note that the 405 clock will not have equal intervals between rising edges. There is a maximum deviation from nominal of about 9ns (1/108MHz). Subjectively this doesn't show on pictures. That's why I multiplied up to 108MHz rather than only to 54MHz.

Another minor point with a DTO is that the output can never be more than half the input frequency. It is possible to get round this but it's ugly.
Another useful little trick. This time about crossing between clock domains. You will inevitably have to take signals between different and async clock domains. If the domains are synchronous then you shoudl have used clock enables so no problem can arise.

For async signals you can simply reclock a signal with the receiving clock and this often works OK. Obviously there will be a jitter equal to 1 period of the receiving clock.  As you raise the frequencies and/or narrow the pulses this starts to become unreliable or even impossible.

The whole subject is actually quite complex(see here for example https://filebox.ece.vt.edu/~athanas/4514...l_cdc.html ) but there is one trick you need to know. The 2 flipflop synchroniser. It's pretty much bomb proof. The first FF is clocked by the input signal while its D input is held at logic 1. Its Q output feeds the 2nd FF's D input. The second FF is clocked by the receveing clock. The important bit is the feedback. The first FF's async clear input is fed by the 2nd FF's Q output. This example uses booleans but it's fine with std_logic.


Code:
-- First FF
process (OUTPUT, INPUT) begin    -- First stage of synchroniser
    if OUTPUT                        then PRE_OUTPUT <= false;    -- Async clear
        elsif rising_edge(INPUT) then PRE_OUTPUT <= true;    -- Set
    end if;
end process;

-- 2nd FF
process (CK) begin

    if rising_edge(CK) then    
        OUTPUT <= PRE_OUTPUT;    -- Just a simple FF

    end if; --CK

end process;
This file could be a bit confusing because it has lots of stuff to do with SDRAM access which is very specific to my board.

Lines 689 -699 are of special interest. This is where I use a DTO to control the vertical size and/or conversion ratio. Iuses a clock enable at H rate (HRESET) so the DTO is incremented once per TV line. The actual addtion is on line 697 where I add SDRAM_VERT_INCREMENT (set in a register previously) to the accumulated value. On line 222 I note that 128 gives no change while 195 is about right for 405 conversion.

For ease of development I made the output of my converter switchable from 405 to 625. Hence I could get most of it working on 625 where I have better testgear. Notably I have the luxury of a serial digital (SDI) output on 625 so I can use the analysis tools I have for this standard. It's fun to adjust SDRAM_VERT_INCREMENT and watch the 625 picture height being expanded or shrunk.

The fractional part of SDRAM_READ_VCOUNT (to the right of the binary point if you wish to look at it that way) drives the interpolator. That fractional part tells you how an output line is placed between inputs lines. A value of 0 means coincident, 1000000 (it's a 7 bit value in my design) means halfway down while 1111111 means almost at the next line. For a 2 line interpolator this can drive the multiplier directly. For 3 or 4 lines it needs an interoplation aperture programmed into a lookup table. I've done some experiments but not come to a conclusion. Choosing the aperture needs care. At one extreme you can reduce vertical resolution too much. At the other extreme (with -ve coeffts for some of the lines) it looks nasty and edgy.

Since the XC2S200 device doesn't have any hardware multipliers I used the Xilinx Coregen tool to build one. I was keen to keep down the size in a small device. In theory I could have just written the mutliplication inline, letting the synthesis tools build one for me. But this would have given a poor result in this device, slow due to lack of pipelining and quite possibly big due to poor optimisation.

The multiplier is instantiated at line 613. I'd planned this design to be expandable to 405 line NTSC hence the note on line 612 about C (Chroma). Because I'm running the 405 section clock at 4x pixel speed, that single multiplier can do all 4 multiplies for a 4 line aperture. (Lines 622-633 are the interpolator)

There's plenty of scope for getting things wrong in a design like this. I made lots of mistakes, which you won't see here. Thank goodness for FPGAs where you can put them right with ease.

Frank, I realise I've thrown a lot of complicated stuff at you to read and digest. If you have any question please feel free to ask.
Hi Jeffrey
Thanks for all the help. Yes there is quite a lot to digest, but that’s a great complaint, its all fascinating stuff. This is turning into a great 'How To' thread.

I will have to read and reread which will take me a while. I think I will have questions, they will most likely be when I try to implement it, as it is then we will find out if I really understand or not.

Frank
Great thing about programmable logic is that you can play with the design so easily. No solderingSmile
I'll tackle conditional statements today. In my own work I use IF all the time, CASE when it's appropraite, WHEN from time to time and WITH almost never.

IF looks pretty obvious. Here's a simple example of a counter which would be in a clocked process:

if COUNT = 14 then COUNT <= 0;
elsif RESET then COUNT <= 0;
else COUNT <= COUNT + 1;
end if;

A couple of wrinkles. The conditions are evaluated in order. Much of the time this doesn't matter but sometimes you can simplify or get caught out. The other is that if there's no ELSE clause and the other conditions aren't met there's an implied hold. For  example:

if J = '1' then Q <= '1';
elseif K = "1" then Q <= '0';
end if

This is a JK flipflop. Since there is no ELSE, if both J and K are zero, Q holds its previous state. J also takes precedence because it's first int he list. So if both J and K are 1 then Q will be set to 1. I sometimes write "else Q <= Q;" explicitly to show the hold but it's not needed.

We've already seen the use of IF to do clocking and clock enable. For clock enable the implicit hold is the important bit. If the flipflop doesn't have a physical CE input it will synthesise logic to do the hold.

If you you use IF outside a process the tools will complain though they will usually give the right answer. If you want to use IF to make purely combinatorial logic (as against clocked) then you should put it in a process with a sensitivity list. If you omit the ELSE clause you will generate a transparent latch, soemthing you may not have intended.

So outside a process you can use WHEN.

X <= A when TEST = '1' else B;

No process needed. It's ugly but it works. You must have an ELSE.

CASE, like iF, normally lives in a clocked process. Same rules as IF when you use it elsewere. My example is from an actual design and shows a CASE within an IF. This is allowed but don't take it too far. It's hard to read and can synthesise badly.

Code:
        if H_LOCK_RATE_IS_MANUAL then H_LOCK_RATE <= H_LOCK_RATE_MANUAL;
            else
                case XSTD is
                    when S1080_60i  => H_LOCK_RATE <= 4;
                    when S1080_50i  => H_LOCK_RATE <= 6;
                    when S1035_60i  => H_LOCK_RATE <= 4;
                    when S1080_24sF => H_LOCK_RATE <= 6;                
                    when S1080_30p  => H_LOCK_RATE <= 4;
                    when S1080_25p  => H_LOCK_RATE <= 6;
                    when S1080_24p  => H_LOCK_RATE <= 8;
                    when S720_60p   => H_LOCK_RATE <= 2;
                    when S720_50p   => H_LOCK_RATE <= 2;
                    when S1080_60p  => H_LOCK_RATE <= 2;
                    when S1080_50p  => H_LOCK_RATE <= 4;        
                    when S720_30p   => H_LOCK_RATE <= 8;
                    when S720_25p   => H_LOCK_RATE <= 10;
                    when S720_24p   => H_LOCK_RATE <= 12;
                    when S525_60i   => H_LOCK_RATE <= 2;
                    when S625_50i   => H_LOCK_RATE <= 2;
                    when others     => H_LOCK_RATE <= 2;    -- Catch all other cases
                end case;
        end if;

The OTHERS clause is essential. If the condition signal is an integer (XSTD is here) you can put multiple values on one line.

    when S1080_50i | S1080_25p | S1080_50p             => HREF_PAT <= 2640 - 1920 - HREF_PATFIX;

There is no precedence due to order of statements. All possible values of the condition signal must be evaluated. If not in the individual lines, then by the catch-all ELSE.

As with IF, there's a similar construct for use outside a process. Again it's ugly. It's called WITH and I've only rarely used it. Here's a very truncated example from a real design. It mutliplexes all the various signals that can be read back by a microprocessor. The full version has over 100 inputs and must use rather a lot of logic. Note that I've expressed the readback addresses in hex. That 16#hhh# is how you express hex integers in VHDL. As against using hex to express a std_logic_vector X"hhh". Yuk.


Code:
with conv_integer(READBACK_ADDRESS) select READBACK_DATA <=
    "000000000" & SDI_INPUT_STANDARD1 when 16#3FC#,
    "000000000" & SDI_INPUT_STANDARD2 when 16#3FD#,
    DE_EMBED_STATUS when 16#97F#,
    SQUAWK_READBACK_DATA(15  downto 0)   when 16#980# to 16#987#,     SQUAWK_READBACK_DATA(31  downto 16)  when 16#988# to 16#98F#,
    SQUAWK_READBACK_DATA(47  downto 32)  when 16#990# to 16#997#,     SQUAWK_READBACK_DATA(63  downto 48)  when 16#998# to 16#99F#,
    CAPTURED_SDRAM_DATA(79 downto 64) when 16#F74#,    CAPTURED_SDRAM_DATA(95 downto 80) when 16#F75#,
-- All other addresses read zero        
    X"0000"        when others;
X <= A when TEST <= '1' else B;

should read

X <= A when TEST = '1' else B;
In VHDL there are several ways of describing an array of bits. The most basic is std_logic_vector. UNSIGNED and SIGNED (2's complement) are different ways of looking at an SLV and can be better when doing arithmetic. INTEGER is still the same array of bits but is a little more complicated. I won't attempt to describe them since there's a rather useful document I found and attached.

I'm a bit old fashioned in my library delcarations and use:

library IEEE;

use IEEE.std_logic_1164.ALL;
use IEEE.std_logic_ARITH.ALL;
use IEEE.std_logic_UNSIGNED.ALL;

There are more modern ways but these can subtly affect the way arithmetic is done and how you cast between the various ways of describing an array of bits. The attached document is very useful on this.

Incidentally NEVER declare:
use IEEE.std_logic_SIGNED.ALL;

It will land you in trouble. You are free to use SIGNED signals regardless.

When doing a multiplier as a video fader it can be useful to have the video as a SIGNED signal (allowing the video to have -ve values, ie go below black) but the control signal is strictly +ve and so is declared UNSIGNED. Can equally use INTEGERS, which can be declared as +ve only or 2's complement.
Pages: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36