****************************************************************************** * Formulas needed to design the VG used in the ZVG * [or: "The Vector Generator Manifesto"] * * Author: Zonn Moore * Date: 02-10-2002 * * (c) Copyright 2002-2012, Zektor. All rights reserved. * * Revision 5 ****************************************************************************** Notes: This document was written over a few months of ZVG development. The math changed as different ideas were tested. Instead of revising the document when the vector math was tweaked on the ZVG, I added addendums to the end of the document. I didn't want to lose the chain of thought, that took me from a more efficient way of running the original Cinematronics VG timers, to the final solution, which runs the VG in the exact opposite way that Cinematronics did. I should have applied for a patent! ;-) Oh well, here's my proof of prior art to keep anyone else from doing so! To understand what the ZVG is really doing, follow the logic to the last addendum. -Zonn ------------------------------------------------------------------------------- Standard RC formulas: Vr = Vo * e^(-t/rc) t = rc * (ln(Vo) - ln(Vr)) rc = t / ((ln(Vo) - ln(Vr))) Tc = ln(Vo) - ln(Vo-Vc) [assumes RC=1, t = Tc*rc] ln(Vr) = ln(Vo) - t/rc Where: Vo = Voltage across full R/C network. Vr = Voltage across R/C's resistor. Vc = Voltage across R/C's capacitor. Vc = Vo-Vr rc = Resistor / Capacitor Time Constant. rc = R*C Tc = Time, in R/C time constants, to achieve voltage Vc. t = Time, in seconds. Facts: It takes 5 time constants to consider a capacitor fully charged. A capacitor charges to 63.2 percent of it's full value in 1 time constant. Its charge rate is "fairly" linear for the time constants under 1. Its charge rate is "quite" linear for the 1st 50% of its voltage value. For a given RC network, a capacitor always takes the same amount of time to reach the same percentage of charge, regardless of the voltage differential. A capacitor going from 0v to 5v would get there in the same amount of time as a capacitor going from 0v to 0.5v, given the same R/C network. Given two identical R/C networks, if the voltage across the 1st cap is plotted in the X axis, and the voltage across the 2nd cap is plotted in the Y axis, a perfectly straight line will be drawn, regardless of the start and ending voltages of the two RC networks. The speed of the line being plotted will slow down, the closer the voltages are to their ending voltages. Some [real life] examples: It's easier to assume all DACs are one volt per division. We're using 12bit DACs, so they swing from -2048 volts to 2047. Max Vector Length = 1023 Double that would be = 2046 Assume a square CRT: -511,511 0,511 511,511 | | | | | | | -------------------+------------------- | | | | | | | -511,-511 0,-511 511,-511 The longest line that can be drawn is 1023 "pixels" across. To allow the line to be drawn in the first 50% of the caps charge voltage we double the voltage of the ending point. 2046 would be doubling, we'll add 2047 to the starting point of any vector (to make use of the full DAC range), then use a timer to draw the vector. This keeps us within the "quite" linear range of the R/C charging. To draw a 1023 pixel vector (X dimension only): Load DAC with -511, charge R/C. Load DAC with -511+2047. Start R/C charging and time vector until R/C circuit reaches the 511 pixel point and stop vector and turn off trace. For shorter vectors load same the DAC values, but stop timer quicker. Timer calculations: Exactly how many time constants for a 1023 length vector. (We add 511 to both values to make starting point 0, it makes calculations easier.) Vo = 2047 Voltage across R/C Vc = 1023 Ending voltage across C Tc = ln(2047) - ln(2047-1023) = 0.69265878006 Time Constants Exactly how many time constants for a 1 length vector. Vo = 2047 Voltage across R/C Vc = 1 Ending voltage across C Tc = ln(2047) - ln(2047-1) = 0.00048863915 Time Constants Since Vo=2047 is now a constant: Tc(n) = ln(2047) - ln(2047-n) What minimum resolution timer is needed to accurately count the above range of times? Counter Resolution = Tc(1023) / Tc(1) = 1417.52616437 = 1418 counts What is the smallest multiplicative factor needed to calculate counts based on Tc? MinTcFactor = 1 / Tc(1) = 2046.49995810 For larger count timers, how is TcFactor calculated? TcFactor = MaxCount / Tc(1023) What timer count should be used for an arbitrary length vector? TimerCount = Tc(vector length in volts) * TcFactor So, timer counts for different vector lengths, using smallest allowable factor are: TimerCount for 1 pixel = Tc(1) * 2046.5 = 1.00 = 1 TimerCount for 511 pixels = Tc(511) * 2046.5 = 587.74 = 588 TimerCount for 1023 pixels = Tc(1023) * 2046.5 = 1417.52 = 1418 What speed should the timer run at? TimerSpeed in MHz = Max Count / Speed of max length vector in microseconds Assuming a full 14.5 inch trace takes 189us to draw (this would be a 1023 pixel vector): FastTimerSpeed = 1418 / 189 = 7.50mhz (Fast Color and most B&W speed) For a 378us trace: SlowTimerSpeed = 1418 / 378 = 3.75mhz (Slow Color and an old B&W speed) [See VECTOR_SLEW_RATES.HTML for typical monitor slew rates.) What RC constant (in microseconds) should be used with each timer value? RC = time for full length line / Tc(1023) FastRC = 189 / Tc(1023) = 272.86us SlowRC = 378 / Tc(1023) = 545.72us Problems: What happens when a line is to be drawn from a point > 0 in a positive direction, since the values greater than 0 cannot have 2047 added to their starting position because of range of the DAC? The maximum positive value a positive direction vector can start at is 510 with a length of 1. So the max that can be added to the vector is 1537. But it's easier in software to test for 511, so for all vectors of 511 and less, the value 1536 is added to the length instead of 2047. Vo = 1536 The longest "short" vector is 511, and Vo is 1536, so the charge on C for a 511 vector is 66.8 percent of Vo, making it slightly longer than 1 time constant putting it in the "fairly" linear range of the RC circuit. The ending speed of the "short" vector traces will be slightly slower than the ending speed of "long" vector traces This is *WAY* below the speed differences on the Cinematronics board so should be unnoticeable. All calculations are the same, using 1536 instead of 2047. Since Vo=1536 is now a constant: Tc(n) = ln(1536) - ln(1536-n) Counter Resolution = Tc(511) / Tc(1) = 618.615507418 = 619 counts MinTcFactor = 1 / Tc(1) = 1562.49995117 You cannot have two different timebases, so how do you pick the proper TcFactor that is based on the same clock as the full length vectors? It works, keep using the original TcFactor for long vectors, only set Vo=1536. Assuming MinTcFactor from above long vector calculations (using Vo=2047) TimerCount for 1 pixel = Tc(1) * 2046.5 = 1.33 = 1 TimerCount for 255 pixels = Tc(255) * 2046.5 = 371.52 = 372 TimerCount for 511 pixels = Tc(511) * 2046.5 = 827.78 = 828 Using the same R/C, drawing "short" vectors takes longer than drawing a short vector using the "long' vector offsets. Using a 7.5mhz clock: Short (Vo=1536) 511 pixel vector = 828 / 7.5 = 110.40 us Long (Vo=2047) 511 pixel vector = 588 / 7.5 = 78.40 us A 30% difference in drawing speeds. The differences in Cinematronics drawing speeds for these same vectors was nearly %60, and since the eye, and CRT phosphors are very non-linear, nobody noticed the differences in brightness between the different drawing speeds. So nobody is going to notice 30%. FWIW this is the same difference in drawing speeds as a diagonal compared with a horizontal/vertical line (1.41 times the distance in the same amount of drawing time.) By keeping our drawing speeds as fast as possible, more vectors can be displayed than using the original Cinematronics methods of variable speed vectors. Two dimensional vectors: When two axis are involved, the longer of the distanced moved is calculated using the above calculations to determine the Vo value and timer count to draw the vector. The shorter distance is done using a simple ratio calculation. ShortDAC = (ShortLen / LongLen) * Vo To draw a vector from 10,20 to 256,128: xLen = 256-10 yLen = 128-20 Since 246 <= 511 Vo = 1536 xDAC = 10 + 1536 = 1546 yDAC = 20 + ((118 / 246) * 1536) = 757 TimerCount = Tc(246) * MinTcFactor = 268 counts Programming details: The DACs being used are single supply only. So 2048 represents the middle of the screen. It is impossible to do two ln() calculations per vector in firmware, so a lookup table must be used. Two tables are needed, one for a Vo of 2047, and one for Vo of 1536. The larger table contains 512 entries, and the smaller 511 entries. The lookup index is the 'length of the largest vector', the table returns the 'number of counts for the timer'. The table conveniently fit inside each other so only a single 1023 entry lookup table is needed. A full lookup would take 2046k of memory, we only have 4096, so some compression needs to be used. The lookup value will always be the same or larger than the index value. So we'll use a byte size table that contains the differences between the index value and the lookup value. The max difference between the largest value and it's lookup is: 1417.52 - 1023 = 395 (If a minimum resolution timer is used) So an additional compare is needed, if the original value is greater than 'n', then add 256 and the value in the lookup table. This uses a table space of 1023 bytes, which is not bad. The divide is unavoidable, but the multiply is by a constant, so can be converted to shift/add/sub instructions. If 'Rw' is a 'working' register, and 'R1' is the value to be multiplied by a constant then to multiply 'R1' by 1536, with the results returned in 'Rw': Rw = R1; Rw <<= 2; Rw -= R1; Rw <<= 9; To multiply R1 by 2047, with the results returned in 'Rw': Rw = R1; Rw <<= 11; Rw -= R1; For integer arithmetic the multiply must come first so: ShortEndDAC = ((ShortLen * Vo) / LongLen) + ShortStartDAC Where the multiply is done using the Add/Sub/Shift method. ------------------------------------------------------------------------------- ADDENDUM I - Running vectors at their maximum possible speeds ------------------------------------------------------------- The above routines were based on using a processor that does not have a multiply instruction. To compensate for the lack of multiply hardware, we used a fixed DAC offset that was smaller than it needed to be on vectors that start on positions greater than 0. By using a hardware multiply instruction it is possible to always use the maximum count on the DAC, thereby always draw the vectors at their maximum possible speeds and linearity, given the resolution of the DACs. So the longest vector that could be drawn would be 1023, and would have to start at position -512, so the maximum value that can be added to the start position would be 2047-(-512), or Vo=2559. The smallest vector that could be drawn is 1, and it could start in position 510, so 2047-510, or Vo=1537. All other vectors lie between these two points. Each vector length count must be recalculated with a different value of Vo. Tc = ln(Vo) - ln(Vo-Vc) Vo = 1536 + length Tcv( n) = ln( 1536 + n) - ln( 1536 + n - n) Tcv( n) = ln( 1536 + n) - ln( 1536) t = rc * Tcv( length) count = t * freq To calculate the R/C needed for a particular speed monitor: rc = full_vector_time / Tcv( 1023) FastRC = 189 / Tcv( 1023) = 370us SlowRC = 378 / Tcv( 1023) = 741us or, full_vector_time = Tcv( 1023) * rc TestSpeed = Tcv( 1023) * 550 = 280.73us (full monitor slew) To calculate the min freq counter needed for a given R/C we now need to calculate the time between two points where the Vo is at it's maximum: MinFreq = 1 / ((Tcv( 1023) - Tcv( 1022)) * rc) To calculate DAC values for a given xs,ys,xe,ye vector (the full kielbasa): ----------------------------------------------------------------------- #include "stdio.h" #include "stdlib.h" void main( int argc, char *argv[]) { int xs,ys,xe,ye; int xlen,ylen,xalen,yalen,xoffs,yoffs; int xdac,ydac,vectlen; if (argc != 5) { fputs( "\nUse: xStart yStart xEnd yEnd\n", stdout); exit( 0); } xs = atoi( argv[1]); ys = atoi( argv[2]); xe = atoi( argv[3]); ye = atoi( argv[4]); xlen = xe - xs; xalen = abs( xlen); ylen = ye - ys; yalen = abs( ylen); if (xalen > yalen) { vectlen = xalen; xoffs = xalen + 1536; yoffs = (yalen * xoffs) / xalen; } else if (xalen < yalen) { vectlen = yalen; yoffs = yalen + 1536; xoffs = (xalen * yoffs) / yalen; } else { vectlen = xalen; xoffs = xalen + 1536; yoffs = xoffs; } if (xlen < 0) xoffs = -xoffs; if (ylen < 0) yoffs = -yoffs; xdac = xs + xoffs; ydac = ys + yoffs; printf( "\nxStart = %4d, xEnd = %4d, Vo = %4d", xs, xdac, abs( xoffs)); printf( "\nyStart = %4d, yEnd = %4d, Vo = %4d", ys, ydac, abs( yoffs)); printf( "\nvecLen = %d\n", vectlen); } ------------------------------------------------------------------------------- Addendum II - Running all vectors at a constant speed ----------------------------------------------------- Upon further reflection, it would seem to be more efficient to run all vectors at the same speed, rather than running the R/C network as fast as it can run. Since all vectors would be of similar speeds, we can set the R/C to match the monitors maximum ratings, and run all vectors at the maximum speed of the monitor. This requires reversing the normal logic of calculating a timer base on a vector length, but instead, using a linear timer, and calculating non-linear DAC settings to compensate for the non-linearities of the R/C network. This requires more convoluted math. The smallest vector that could be drawn is still 1, and it can start in position 510, so 2047-510, or Vo=1537 is the largest value allowed on the DAC. This also corresponds to the slowest drawing speed of all vectors, so we use this as the baseline. All vectors will be compensated to draw at the same average speed as a vector length of 1. First, to determine the R/C constant needed to run a monitor a particular speed. Assumes a 19" monitor, and that there is 14.5" of viewable width. To find the time of vector with the length of 1 measured in us per inch: t = (us_per_inch / (1023 / 14.5)) / 1000000 The minimum frequency clock needed for this resolution vector is: minFreq = 1 / t To find the R/C needed to run a monitor at this specific number of us's per inch: rc = t / (ln( 1537) - ln( 1536)) And for the grand finally, find the value to be added to the DACs start position to draw a vector of length 'vLen', that finishes in 't*vLen' time: Vo = (vLen / (1 - (1 / e^((t * vLen) / rc))) - vLen Given a Monitor Speed of: 20us/inch, find the Vo of a vector of length 1023: t = 0.283 us rc = 436 us Vo = (1023 / (1 - (1 / e^((t * 1023) / rc))) - 1023 ------------------------------------------------------------------------------- Addendum III -- Adding resolution --------------------------------- There is no reason to stop at 1024 points. The resolution can be increased by simply increasing the number of points. However as the points increase, the linearity of the vectors decrease. The larger the ending voltage used when drawing a vector, the more linear the vector will be drawn. When using more of the DAC as starting points, the ending counts of the DAC are closer to the starting points, and the vectors become more non-linear (the brighter the trace will be toward the end of the vector as the trace slows down). Example of a 1200 point system: A 1200 point system has the advantage of being able to maintain a 1024x768 on screen resolution, but still allow overscaning to be done on game like Tempest. The smallest vector that could be drawn is still 1, and it can start in position 598, so 2047-598, or Vo=1449 is the largest value allowed on the DAC. This also corresponds to the slowest drawing speed of all vectors, so we use this as the baseline. All vectors will be compensated to draw at the same average speed as a vector length of 1. First, to determine the R/C constant needed to run a monitor a particular speed. Assumes a 19" monitor with 14.5" of viewable width. To find the time of vector with the length of 1 measured in us per inch: t = (us_per_inch / (1199 / 14.5)) / 1000000 The minimum frequency clock needed for this resolution vector is: minFreq = 1 / t To find the R/C needed to run a monitor at this specific number of us's per inch: rc = t / (ln( 1449) - ln( 1448)) To find the value to be added to the DACs start position to draw a vector of length 'vLen', that finishes in 't*vLen' time: Vo = (vLen / (1 - (1 / e^((t * vLen) / rc))) - vLen Given a Monitor Speed of: 20us/inch, find the Vo of a vector of length 1199: t = 0.242 us rc = 436 us Vo = (1199 / (1 - (1 / e^((t * 1199) / rc))) - 1199 ------------------------------------------------------------------------------- Epilogue -------- The ZVG uses the calculations described in Addendum III. It uses a lookup table that is used to lookup the ending value of an X or Y DAC based on the X or Y length of the vector. It's created using the function: for (vLen = 0; vLen < 1200; vLen++) VoTable[vLen] = (vLen / (1 - (1 / e^((t * vLen) / rc))) - vLen; The timer setting is simply: Timer = t * vLen; Where: t = (us_per_inch / (1199 / 14.5)) / 1000000 And the R/C constant used is: rc = t / (ln( 1449) - ln( 1448)) All vectors are drawn at a constant speed. When height and width are adjusted properly there is a standard 1024x768 starting point resolution on the visible screen. With a full resolution of 1200x900. This allows full overscan for Tempest like games, while still allowing point perfect emulation of games like the Cinematronics and Sega series. If the R/C constant is picked properly for the monitor, you can run all vector monitors near their maximum speeds, allowing more vectors per refresh than the standard vector generators if yesteryear, which in turn allows the running of faster games on slower monitors. ------------------------------------------------------------------------------- Finis!