Page 2 of 3
Re: VGA Work
Posted: Mon Oct 27, 2025 9:47 pm
by barnacle
What I'm seeing is the picture will drop for a couple of seconds.
A classic symptom of a display being unhappy with the syncs for some reason... (though it could be e.g. a complete loss of signal for other reasons).
If you are using 25 MHZ, you will lose 8 pixels on the horizontal, which would account for why we are only seeing 39 characters.
Not necessarily; I would expect the signal to be generated from a constant divider of 800 pixels per line, irrespective of the actual dot clock frequency. In which case, the signal would be slightly slower than expected, but I'd expect many or all displays to cope with it.
(I think I have elsewhere on these pages document ideas for VGA derived from a 14.745600MHz clock, which conveniently divides down to 115200 baud. The timing is definitely obscure, and doesn't divide exactly - 468 (and a bit) clocks across the complete line, so 58 or 59 character cells across. Which gives 46 cells visible on the active picture area.)
Neil
Re: VGA Work
Posted: Mon Oct 27, 2025 10:10 pm
by J64C
Not necessarily; I would expect the signal to be generated from a constant divider of 800 pixels per line, irrespective of the actual dot clock frequency. In which case, the signal would be slightly slower than expected, but I'd expect many or all displays to cope with it.
What do you mean not necessarily? The standard gives you 25.422 uS to display 640 pixels. If you are taking 0.04 seconds to display a pixel you will loose the trailing 8 pixels. There's no ambiguity about that.
I'm more concerned with the 1.0v spikes during the sync pulses. That will most definitely cause confusion for the monitor as the sync pulses are TTL level.
Personally, I'd hook up another probe and see what signal coincides with that spike and put a 50 ohm resistor inline with that source.
Re: VGA Work
Posted: Tue Oct 28, 2025 12:49 am
by gfoot
For the 1V spikes - how are you calculating when to set vsync - is it asynchronous combinatorial logic? That can certainly cause little spikes when two bits transition in opposite directions. It is much more reliable to use registered outputs for this, synchronised to the VGA clock. The 1502 even has T-flipflop as an option I believe so you can just tell it what count values you want it to toggle on.
The left most character seems to be completely missing, I'm convinced this is because I'm trying to load that character right on the zero horizontal count, causing me miss the first one as the very slow ROM tries to settle out.
I started making my horizontal counters reset at the start or end of the sync pulse rather than the start of the visible display, for this sort of reason - it's a lot easier to arrange for the video RAM to get ready ahead of the first visible pixel, with the vertical count stable already (it's really easy to accidentally have it display the first character of the previous scanline instead...). Then on each memory clock cycle (usually the VGA clock divided by 2, 4 or 8 depending on the output circuit) I latch the value coming from the RAM and increment the address counter simultaneously so that the RAM can start looking up the next data value. This latching is done into either a shift register (like yours) or one or more registers of D flip-flops, depending on the specific output circuit.
In a sense the displayed data is one memory clock cycle (e.g. character) too late - but it's usually easy enough to adjust the sync timings to balance that out (i.e. changing the porch durations to shift the display a character to the left).
Re: VGA Work
Posted: Tue Oct 28, 2025 2:12 am
by Yuri
For the 1V spikes - how are you calculating when to set vsync - is it asynchronous combinatorial logic? That can certainly cause little spikes when two bits transition in opposite directions. It is much more reliable to use registered outputs for this, synchronised to the VGA clock. The 1502 even has T-flipflop as an option I believe so you can just tell it what count values you want it to toggle on.
It is a register the current Verilog is as follows:
Code: Select all
module vga_char
(
//input wire RESETb, // Reset bar
input wire VGA_CLK, // 25.175 Mhz clock
output reg[9:0] HC, // Horizontal counter
output reg[9:0] VC, // Vertical counter
output reg CHAR_LD, // Character load
output reg BLANK, // Blank bar (output disable)
output reg HSb, // Horizontal Sync bar
output reg VSb, // Vertical Sync bar
output wire OUT_CLK // Delayed clock
);
// Resultion is for 640x480 @ 60Hz
/*
* 0 48 688 704
* | back | VISIBLE_AREA_PORTION | front | sync |
* | porch | | porch | pulse |
*/
// Operational constants
parameter HORZ_TOTAL = 800;
parameter HORZ_RES = 640;
parameter HORZ_BACK = 48;
parameter HORZ_SYNC = 96;
parameter HORZ_FRONT = 16;
parameter HORZ_SYNC_START = HORZ_RES + HORZ_FRONT;
parameter HORZ_SYNC_END = HORZ_TOTAL - HORZ_BACK;
//parameter HORZ_VIS_START = HORZ_BACK - 1;
//parameter HORZ_SYNC_START = (HORZ_TOTAL - HORZ_SYNC) - 1;
//parameter HORZ_VIS_END = HORZ_SYNC_START - HORZ_FRONT;
parameter VERT_TOTAL = 525;
parameter VERT_RES = 480;
parameter VERT_BACK = 33;
parameter VERT_SYNC = 2;
parameter VERT_FRONT = 10;
parameter VERT_SYNC_START = VERT_RES + VERT_FRONT;
parameter VERT_SYNC_END = VERT_TOTAL - VERT_BACK;
//parameter VERT_VIS_START = VERT_BACK - 1;
//parameter VERT_SYNC_START = (VERT_TOTAL - VERT_SYNC) - 1;
//parameter VERT_VIS_END = VERT_SYNC_START - VERT_FRONT;
wire horz_valid, vert_valid;
always @(posedge VGA_CLK)
begin
if (HC == HORZ_TOTAL - 1)
begin
// End of horizontal line, increment vertical
HC <= 10'd0;
if (VC == VERT_TOTAL - 1)
begin
VC <= 10'd0; // End of vertical, reset
end
else
begin
VC <= VC + 1;
end
end
else
begin
HC <= HC + 1;
end
horz_valid <= (HC < HORZ_RES);
vert_valid <= (VC < VERT_RES);
HSb <= (HC >= HORZ_SYNC_START) && (HC < HORZ_SYNC_END) ? 1'b0 : 1'b1;
VSb <= (VC >= VERT_SYNC_START) && (VC < VERT_SYNC_END) ? 1'b0 : 1'b1;
BLANK <= !(horz_valid && vert_valid);
end
assign OUT_CLK = HC[0];
always @(posedge OUT_CLK)
begin
CHAR_LD <= !BLANK && // Load only when not blanking
HC[3:1] == 0; // and on zero pixel
end
endmodule
I'm sure I'm doing all sorts of things in here I shouldn't be, I'm still very new to writing Verilog.
The left most character seems to be completely missing, I'm convinced this is because I'm trying to load that character right on the zero horizontal count, causing me miss the first one as the very slow ROM tries to settle out.
I started making my horizontal counters reset at the start or end of the sync pulse rather than the start of the visible display, for this sort of reason - it's a lot easier to arrange for the video RAM to get ready ahead of the first visible pixel, with the vertical count stable already (it's really easy to accidentally have it display the first character of the previous scanline instead...). Then on each memory clock cycle (usually the VGA clock divided by 2, 4 or 8 depending on the output circuit) I latch the value coming from the RAM and increment the address counter simultaneously so that the RAM can start looking up the next data value. This latching is done into either a shift register (like yours) or one or more registers of D flip-flops, depending on the specific output circuit.
In a sense the displayed data is one memory clock cycle (e.g. character) too late - but it's usually easy enough to adjust the sync timings to balance that out (i.e. changing the porch durations to shift the display a character to the left).
I swapped out my oscillator (Which is 25.175MHz as specified in my schematic BTW) with an Arduino programmed as a makeshift pulse generator. I can type in a number and get an exact count from the Arduino which was super helpful in this case.
This shows basically exactly this, I was concerned I would need to setup the addresses well in advance of when I tell the shift register to load. Effectively I need to do this a character before as you put it; so I'll contemplate how I can adjust my sync pulses and blanking logic to account for that and do that.
Re: VGA Work
Posted: Tue Oct 28, 2025 4:51 am
by J64C
I swapped out my oscillator (Which is 25.175MHz as specified in my schematic BTW) with an Arduino programmed as a makeshift pulse generator. I can type in a number and get an exact count from the Arduino which was super helpful in this case.
Ah cool. I didn't see this.
If you place a 100 ohm resistor between the oscillator and the CPLD, that will quieten down your circuit significantly and may even resolve your spikes.
This was a before and after on stripboard. I'll see if I can find a pic of what it looks like now on my proper PCB.
Any noise you can get rid of goes a long way.
Re: VGA Work
Posted: Tue Oct 28, 2025 5:47 am
by J64C
Horizontal is showing right around 3.9 usec (VGA Timing says it should be 3.81 usec)
That's still off by two clock cycles, which won't make a world of difference, but considering you are using a CPLD and the correct base clock, you should be able to make these timings exact. Which tells me that you have two "off by one's" in your Verilog.
[edit] I'm just setting up my rig to see if my Verilog will be any good to you.

Re: VGA Work
Posted: Tue Oct 28, 2025 6:45 am
by barnacle
Not necessarily; I would expect the signal to be generated from a constant divider of 800 pixels per line, irrespective of the actual dot clock frequency. In which case, the signal would be slightly slower than expected, but I'd expect many or all displays to cope with it.
What do you mean not necessarily? The standard gives you 25.422 uS to display 640 pixels. If you are taking 0.04 seconds to display a pixel you will loose the trailing 8 pixels. There's no ambiguity about that.
I think we may be confusing each other. The standard gives timings based on 800 pixels per line, of which 640 are reserved for the image data. If your clock rate is inexact, but the signals are coming from a divider chain, then the overall frequency is wrong but there are still 640 pixels being emitted - they just don't take the length of time that the standard expects them to take.
But this is an analogue standard. The display scan time is set by the interval between the syncs, not the exact timing of those syncs, and on the originally intended CRT displays, there is no internal reference to blanking period at all. The signal to the tube base is either black - through the blanking periods - or has information (which may be black) and that information will light the screen. And it will light it whenever it occurs: if you pulse the video signal during flyback, you'll see it. If your image is shifted 10us relative to where it should be, you'll see it shifted by a third of the screen.
The digital display has to work out where the 'beam' is. It has to know how to isolate the active line from the blanking, which again, is not a signal presented to it. One approach might be to take a very stern view about the timings, and sample 640 times (with appropriate stretching) across the line based on an exact interval after the line sync pulse. This would indeed lose pixels if the dot clock is too slow, but it would also lose pixels at one end or the other of the line if there is any error in the sync timing. A better approach might be to look for active picture - the first and last non-black pixels on the line - across a field or two and derive internal timings from that.
I suspect that the monitor I use for testing uses both methods. It has an internal model of where things should be, and until the auto button is pressed that's how it displays. An off-frequency input is displayed shifted, with black pixels at one side or the other. When the auto button is pressed, things shimmy around for a second or two while it does a phase lock and at that point it displays the full pixels in the full width of the screen. Thereafter the settings are stored internally and remembered for next time.
Things can get confused with pathological signals, for example a mostly black screen with a flashing cursor in one corner, or colour bars. With the first, I think it just assumes standard timings, and won't show any issue until you fill the screen to both edges (at which point you can push the auto button if necessary now it has something to chew on!) but colour bars traditionally begin with a black bar. Auto on that, and the black bar disappears (which itself suggests a very wide range of adjustment in the PLL in the display). If you have colour bars with a red section on the lower quarter of the screen, then that extends the full picture width and there's no problem.
As an aside regarding timings, although TinyVGA.com indicates timings in pixels, all those timings are actually related to 8-pixel-wide character cells, which I find much easier to think in since that's directly related to when you need to sample video ram. I don't know much (anything) about CPLD but I suspect smaller numbers are easier to manage.
Neil
Re: VGA Work
Posted: Tue Oct 28, 2025 8:38 am
by J64C
Have a play around with this if you like. This will give you perfect hSync and vSync timing anyway on a 25.175 input.
I noticed I was getting spikes like yours in the vertical sync area, which were identical spacing to what you have too. This was caused by not using the ground right next to my signals (was using a ground point near the power input for convenience), with the spiking being noise from the hSync pulses. Once I moved the ground to a better spot the signal cleared right up. So, you may not have an issue with the spikes at all, they may not be there in reality.
Code: Select all
`timescale 1ns / 1ps
module top_module
(
input CLK,
output reg HSYNC,
output reg VSYNC
);
reg [11:0]hCounter = 'd0;
reg [9:0]vCounter = 'd0;
always @(negedge CLK)
begin
if(hCounter >= 'd0 && hCounter < 'd95) // Horizontal Sync
HSYNC = 0;
else
HSYNC = 1;
hCounter = hCounter + 1;
if(hCounter == 'd800) // End of horizontal line
begin
hCounter = 'd0;
vCounter = vCounter + 1;
end
if(vCounter >= 'd0 && vCounter < 9'd2) // Vertical sync
VSYNC = 0;
else
VSYNC = 1;
if(vCounter > 'd525) // End of vertical lines
vCounter = 0;
end
endmodule
Re: VGA Work
Posted: Tue Oct 28, 2025 9:41 am
by J64C
As an aside regarding timings, although TinyVGA.com indicates timings in pixels, all those timings are actually related to 8-pixel-wide character cells, which I find much easier to think in since that's directly related to when you need to sample video ram.
Related, loosely, but that’s where it ends. There is no requirement to have 8 pixel wide characters (or characters at all). The VGA spec just gives you a canvas of screen width, screen height, along with blanking and sync. It’s up to the end user what they wish to do with that canvas.
Re: VGA Work
Posted: Tue Oct 28, 2025 10:18 am
by barnacle
Oh, no argument. It's an analogue signal. But I'll bet that's what they were thinking when they designed the specification.
Neil
Re: VGA Work
Posted: Wed Oct 29, 2025 3:34 am
by Yuri
Well making a bit of progress; I've been fiddling with the math some as well as learning a bit more about the nature of Verilog. No good visible progress, but I am getting there I think.... *squints at the monitor*
The big thing I've learned is that despite my best efforts to update the BLANK (or BLANKb depending on how I have the circuit at the moment) and the sync pulses it seems they don't want to reflect the current value of the counters until after the fact.
E.g.:
Code: Select all
// Partial verilog
always @(posedge VGA_CLK)
begin
// Update counts here.
BLANKb <= !(HC >= 15); // This won't go low until HC == 17!
end
However this does work as expected:
Code: Select all
// Partial verilog
always @(posedge VGA_CLK)
begin
// Update counts here.
end
always @(HC[0])
begin
BLANKb <= (HC >= 15); // Now it goes low on 16 as expected.
end
Re: VGA Work
Posted: Wed Oct 29, 2025 5:02 am
by J64C
Dunno. I tend to avoid the quirky Verilog syntax in favour of the more C style in the example I posted.
And with the latter, I'd expect that to be running at half clock rate. Assuming HC increments with the base clock.
Did you try my example? As I said, the timing is perfect on that. I wrote it and tested it last night, with a flicker free 640 x 480 60Hz screen right next to me.
Lastly, there is a crap-ton that can be optimised in your initial Verilog code that can be optimised, freeing up heaps of space. As space is a premium in CPLD's.
Re: VGA Work
Posted: Wed Oct 29, 2025 5:50 am
by barnacle
Just for curious: are you using the CPLD to generate all the signals? By which I mean not just the syncs and blanking, but the memory addressing and any control signals? I'm pretty certain that whatever you do - unless you have the parallel to serial converter also on the CPLD - you'll need to emit the blanking to the rest of your circuit, but it could easily be a mixed blanking signal, no?
Neil
Re: VGA Work
Posted: Wed Oct 29, 2025 7:09 am
by J64C
Personally, I don't even bother taking the blanking periods in to account. If the horizontal counter isn't within the visible range, don't display anything.
Re: VGA Work
Posted: Wed Oct 29, 2025 11:25 am
by gfoot
The big thing I've learned is that despite my best efforts to update the BLANK (or BLANKb depending on how I have the circuit at the moment) and the sync pulses it seems they don't want to reflect the current value of the counters until after the fact.
E.g.:
Code: Select all
// Partial verilog
always @(posedge VGA_CLK)
begin
// Update counts here.
BLANKb <= !(HC >= 15); // This won't go low until HC == 17!
end
That is what I would expect - though I don't know verilog very well, it is also what would happen in cupl, or for the most part, in a less integrated circuit with individual flipflops. On the rising edge of the clock, all the flipflops that depend on it have their states updated based on their current data input levels, and these data input levels are the levels from before the updates occur. So BLANKb's state is being set based on the existing value of the counters before the clock edge; simultaneously, the counter values are also being updated, but anything synchronous to the same clock signal will have its state set based on the old values.
Your second example was making the BLANKb flipflop be clocked by the low bit of the counter. This might not be a good idea because the other counter bits may be in flux at the time BLANKb is updated, violating setup time requirements for BLANKb. (It might be somehow fine for this device, I am not familiar with it - but in general you should avoid having the input for a registered output be unstable in the lead-up to the clock signal arriving.)
In cupl, where I have something like a counter that's being updated on a clock signal and also other registered outputs that depend on the counter's value and are updated on the same clock edge, I often precalculate the new counter value in a cupl internal variable, so that I can use it both as the update value for the counter, and as an input to expressions that determine the states of other registered outputs. Here's an example from a VGA text memory control PLD, with a 3-bit counter C0,C1,C2 and various control signals that need to activate at different points in the count cycle - note that 'nC0', 'nC1', 'nC2' are just cupl variables that represent the 'next' states of the counter bits:
Code: Select all
nC0 = !C0;
nC1 = C0 & !C1 # !C0 & C1;
nC2 = C0 & C1 & !C2 # !C0 & C2 # !C1 & C2;
Field C = [ C2..0 ];
Field nC = [ nC2..0 ];
C.d = nC;
XOE.d = WP & nC:5 # XOE & nC:[4..6];
RAMWE.d = XOE & nC:6;
SRPE = !CLK1 & C:2 # CLK1 & C:3;
nC is used as the input state for C ("C.d = nC") and also in the expressions for other registered outputs' D inputs (XOE and RAMWE) while C is used directly for SRPE because that's not a registered output.
It doesn't make any difference to the logic expressions that are generated and written to the device, but I find it makes the code a bit easier to read rather than having to write (and read!) the expressions based on old counter values.