VGA Work

For discussing the 65xx hardware itself or electronics projects.
User avatar
Yuri
Posts: 371
Joined: 28 Feb 2023
Location: Texas

Re: VGA Work

Post by Yuri »

J64C wrote:
Dunno. I tend to avoid the quirky Verilog syntax in favour of the more C style in the example I posted.
"Quirky?" In what way?
Quote:
And with the latter, I'd expect that to be running at half clock rate. Assuming HC increments with the base clock.
It does update with the base clock (as per the initial Verilog code).
Quote:
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.
I have not, I'm not sure how this would actually help me. Using it as is, would not tell me if it was working or not, I need those other signals to generate something to see.

I would have no picture to go off of to see if it was still dropping out. (The sync light is very laggy and isn't a good indicator) The timings on the scope might say one thing, but the monitor I'm using could very well paint a very different picture. (No pun intended)

Further, I do need those counts to start at 0 on the visible section; along with all the other signals I'm trying to generate. Thus I would have to make significant changes to make it do what I need, likely breaking it back into the non functioning state.
Quote:
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.
Okay... I expected there would be things I need to optimize; however, I would rather get it working first before dealing with optimizing it.
barnacle wrote:
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?
Yes, please see the schematic; though a bit out of date now, but is still relevant.
Quote:
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?
Correct, and that's why I was checking to see what I was getting to the blanking signal. Effectively this is where the one offs are coming from. (I'll get more into that in a moment.)
J64C wrote:
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.
Not sure how you would do that if you aren't outputting that to the rest of the circuit. >_<
gfoot wrote:
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.
Correct, I have sense as of the last posting looked this up and gained a bit more understanding of it.

In Verilog <= is an asynchronous assign. The cupl equivalent of:

Code: Select all

X.d = Y;
A = X; // A would get old value of X  (or something like this, might not be correct on syntax)
= is a sequential assign, which guarantees the order and causing the statements there after to get the updated values.

I somewhat knew about this going into writing this thing, but was not totally clear on how it all fits together in the realm of Verilog, as such my first solution I came upon was to trigger on HC[0], though I highly doubt this is the correct way. (Works on the Arduino clock at human speeds, likely will fail when I speed it back up to 25.175MHz)
Quote:
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.)
That is quite possible. (See above) It's all part of the learning experience right now.
Quote:
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
That might be the better approach which I will explore.

Another idea I had was to effectively anticipate when those signals would actually change. I'm not totally sold on this idea though, I fear what that is making me do is second guess what the hardware might do, and that doesn't seem like a solid design.
J64C
Posts: 239
Joined: 11 Jul 2021

Re: VGA Work

Post by J64C »

Yuri wrote:
J64C wrote:
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.
I have not, I'm not sure how this would actually help me.
My timing is correct. Yours isn't.
Yuri wrote:
Further, I do need those counts to start at 0 on the visible section; along with all the other signals I'm trying to generate. Thus I would have to make significant changes to make it do what I need, likely breaking it back into the non functioning state.
So, if it is stable and working. What are we asking here?
Yuri wrote:
J64C wrote:
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.
Not sure how you would do that if you aren't outputting that to the rest of the circuit. >_<
Looking at the circuit diagram you have provided, even you aren't using the blanking signal.
User avatar
Yuri
Posts: 371
Joined: 28 Feb 2023
Location: Texas

Re: VGA Work

Post by Yuri »

J64C wrote:
Yuri wrote:
I have not, I'm not sure how this would actually help me.
My timing is correct. Yours isn't.
Near as I can tell my counts are fine, it's my understanding of how Verilog works that was off.

Going over your code in detail shows some rather odd things though, further adding to the confusion.

Yours

Code: Select all

if(hCounter >= 'd0 && hCounter < 'd95); // 95 pixels?

...

if(vCounter >= 'd0 && vCounter < 9'd2); // But 2 lines
Further:

Code: Select all

if(hCounter == 'd800) // Triggering right on 800 count, okay

...

if(vCounter > 'd525) // Triggering on count 526?
My code

Code: Select all

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;
Mine (constant's subbed in)

Code: Select all

HSb <= (HC >= 656) && (HC < 752) ? 1'b0 : 1'b1;
VSb <= (VC >= 490) && (VC < 492) ? 1'b0 : 1'b1;

Mine (constant's subbed in and offset to start at zero like you have)

Code: Select all

HSb <= (HC >= 0) && (HC < 96) ? 1'b0 : 1'b1; // 96 pixels
VSb <= (VC >= 0) && (VC < 2) ? 1'b0 : 1'b1;  // 2 lines
(Don't ask me why I elected to do that with a trinary operator instead of just negating it; IDK, probably too much C# in my life....)

My ending checks

Code: Select all

if (HC == HORZ_TOTAL - 1) // Triggering on 799

...

if (VC == VERT_TOTAL - 1) // Triggering on 524
My testing shows on my scope that the signals I'm generating and the ones you are generating are close.

(Pixels here are calculated as time measured divided by 39.721946375372ns (or 25.175MHz))
(I should note that I figured out how to get much more accurate data out of my scope this time around.)

Yours:

Code: Select all

Horzontal   Time (usec)        Pixels
sync        3.79               95.4133
line        31.78              800.0615

Vertical    Time               Lines
sync        63.53usec          1.9991
frame       16.71ms            525.8023

59.83 Hz (from scope)
Mine:

Code: Select all

Horzontal   Time (usec)        Pixels
sync        3.82               96.1685
line        31.78              800.0615

Vertical    Time               Lines
sync        63.57usec          2.0003
frame       16.68ms            524.8584

59.94 Hz (from scope)
From tinyvga.com (for reference, not computed in my tests)

Code: Select all

Horzontal   Time (usec)        Pixels
sync        3.8133068520357    96
line        31.777557100298    800

Vertical    Time (ms)          Lines
sync        0.063555114200596  2
frame       16.683217477656    525

60 Hz
(I'd kill for a table markdown.... >_<)

However, yours math's out to about 95 pixels in the sync pulse, as expected.

The total pixels per line are exactly the same. This is expected. We are both reaching the same net result.

You are incrementing always and then checking for 800. I am checking for 799 and incrementing only if I haven't reached that point.

There are however more lines per frame in your measurements, also as expected.
Quote:
Yuri wrote:
Further, I do need those counts to start at 0 on the visible section; along with all the other signals I'm trying to generate. Thus I would have to make significant changes to make it do what I need, likely breaking it back into the non functioning state.
So, if it is stable and working. What are we asking here?
I believe I did say the image got much more stable prior to you suggesting my counts were off; I also specifically mentioned that I was pretty sure that I was sending pixel data during the blanking/syncing signals which is thowing off the black balance dection; and I even mentioned that displaying different characters seems to exacerbate the issue.

So in short, it's that BLANK signal that you left out that I'm pretty sure is the problem.
J64C wrote:
Yuri wrote:
J64C wrote:
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.
Not sure how you would do that if you aren't outputting that to the rest of the circuit. >_<
Looking at the circuit diagram you have provided, even you aren't using the blanking signal.
Very first post:
Yuri wrote:
I suspect right now the OE# of my 74HC574 is slightly off from the blanking signals causing the monitor to see some output from the picture before the '574 releases the circuit.
Later post:
Yuri wrote:
I will be putting the D-flipflop back in shortly, mostly so I can get some color support which will do some DAC look ups.
That 74HC574 has sense made a reappearance. So yes, the circuit as I drew it at that time did not have the '574, but it is something I am periodically using and checking.

BUT those counts are in the schematic, so having them at zero is important still; regardless of the BLANK (or sometimes BLANKb depending on what I'm trying to test).

The CHAR_LD signal is also being used to signal to the shift register when it should load a line from the character ROM; that is also important, which in turn is also reusing the logic from the BLANK signal to know if it should even bother sending that signal at all. (Also mentioned in a previous post)
J64C
Posts: 239
Joined: 11 Jul 2021

Re: VGA Work

Post by J64C »

Glad you are all up and running.
User avatar
Yuri
Posts: 371
Joined: 28 Feb 2023
Location: Texas

Re: VGA Work

Post by Yuri »

Okay, after installing Verilator and checking my deisgn, I have worked out what was going on with that BLANK signal. Helps to pick good names for what you're using it for, I was tripping myself up with the name and how I was actually using it..... >_<

That sorted it seems like the other contributing factor was the rather dodgy connections with the dupont cables and my breadboards.

I've spent some time replacing most of those with actual cut wires and that has helped quite a bit. The image has stabilized greatly from that alone, though I do still get quite a bit of noise on the few that remain, I'll get to replacing them at some point soon.
photo_2025-11-04_00-18-47.jpg
I also set it up to pull characters from RAM.
photo_2025-11-04_00-16-13.jpg
The RAM is currently uninitialized so all I get right now is random garbage, but I can at least see the characters.

My next goal will be to adjust some of the counter outputs so the memory laid out in one continuous line to make the math on the 6502 easier; I will also be adding 4-bit color as well. The memory layout will be the same as text mode on the IBM PCs, as that is what I'm familiar with.

I also need to set it up so I can actually read/write to this memory. I'm still trying to work out how I'd like to to do this.

One idea was maybe to use the RDY to block the 6502 while the CPLD is accessing it. As the CPLD only really needs it for a grand total of 2 full resolution VGA clocks (one to get the character, and the other to get the color) this shouldn't present too much of a delay. However, my concern is that if the 6502 itself grabs the RAM first it could end up holding on to it for too long and thus throwing off the pixel timings.

Another thought would be to buffer the 6502 access through some additional latches or a FIFO, but that seems like it might get really complicated quickly.

Or Dual-Port RAM.... but where's the fun in that? (Famous last words)
gfoot
Posts: 871
Joined: 09 Jul 2021

Re: VGA Work

Post by gfoot »

Yuri wrote:
Okay, after installing Verilator and checking my deisgn, I have worked out what was going on with that BLANK signal. Helps to pick good names for what you're using it for, I was tripping myself up with the name and how I was actually using it..... >_<
One thing I was going to mention there is that when I'm making logic designs I almost always use positive logic, and have the signals be inverted at the input or output pins if that's the required electrical interface. I think I saw you were tracking an inverted blank signal in the code itself, which can make it harder to reason about the logical expressions it is part of. Or maybe I'm misremembering - just another tip, anyway!
Quote:
That sorted it seems like the other contributing factor was the rather dodgy connections with the dupont cables and my breadboards.

I've spent some time replacing most of those with actual cut wires and that has helped quite a bit. The image has stabilized greatly from that alone, though I do still get quite a bit of noise on the few that remain, I'll get to replacing them at some point soon.
These things can vary a lot, but individual wires are usually the most reliable so long as the gauge is fairly low. Higher gauges can give mixed results and then results probably depend on the quality of the breadboard itself. That said I've used all sorts of things on both cheap and expensive breadboards and one way or another the problems are generally possible to resolve with some rewiring if necessary.
Quote:
I also set it up to pull characters from RAM.
It looks nearly perfect - you have the right number of characters (40x30 at 16 pixels square) and the characters look mostly artifact-free. I do see these issues which you may or may not care to deal with at this stage:
  • The top scanline of VGA pixels appears to be coming from the bottom scanline of the first character row, rather than the top one
  • The bottom scanline and rightmost column of VGA pixels appear to be missing
  • The first character on each row seems to be struggling a bit with the scanline doubling
I would try adjusting the display up and left to see whether the bottom row and rightmost column are off the screen, or whether you're not outputting them.

For the top scanline and issues with scanline doubling down the leftmost column, I'd suspect these are caused by having your horizontal counters reset (and vertical counters increment) at the same point as the visible display area begins. It can lead to using the previous scanline's data for the first character of each row (including for example which character was read from RAM, for the top scanline of the character; I can imagine that the scanline-doubling issues could also be caused by something like this) You may be able to adjust the code to update the relevant signals one character earlier to get around this.

I have found that in some cases these kinds of artifacts seem to be caused by the monitor itself, and can't be fixed. For example I have a monitor that insists on delaying its very first scanline by one pixel; if I use the monitor controls to shift the display down a scanline, so that it is not on the first physical row of pixels the monitor has, then the artifact goes away. But usually they are issues with my signal which I can fix if I do the right diagnosis and have enough patience.
Quote:
My next goal will be to adjust some of the counter outputs so the memory laid out in one continuous line to make the math on the 6502 easier; I will also be adding 4-bit color as well. The memory layout will be the same as text mode on the IBM PCs, as that is what I'm familiar with.
This can be more complicated than you might think when you are doing scanline-doubling or text-mode output, as you need to scan the same range of memory addresses multiple times on a range of scanlines - when you come to each subsequent scanline, you'll need to reset the address counters to the values they had at the start of the previous scanline, which is not going to be a nice round number. I've thought of a few ways to do this in the past (including of course just storing the right value and initialising the counters to that value on subsequent scanlines) but so far I have backed out from actually doing it because of the added complexity. Instead I embrace the possibility of having some room for horizontal scrolling, or failing that, just spare RAM for other purposes!

It might not be such an issue for you though if you have enough spare resources in the CPLD.
Quote:
I also need to set it up so I can actually read/write to this memory. I'm still trying to work out how I'd like to to do this.

One idea was maybe to use the RDY to block the 6502 while the CPLD is accessing it. As the CPLD only really needs it for a grand total of 2 full resolution VGA clocks (one to get the character, and the other to get the color) this shouldn't present too much of a delay. However, my concern is that if the 6502 itself grabs the RAM first it could end up holding on to it for too long and thus throwing off the pixel timings.

Another thought would be to buffer the 6502 access through some additional latches or a FIFO, but that seems like it might get really complicated quickly.
I've always avoided pausing the CPU clock. Most of my circuits have instead had it synchronised (i.e. driven by) the VGA clock, with careful timing so that any data written by the CPU arrives at the right time between reads by the video circuit. My most recent circuits have been asynchronous, however, capturing the CPU's write request in buffers and applying it to video RAM at the next opportunity, allowing the CPU clock to be independent of the video circuit.

Reading is harder to support than writing and you might consider just not supporting it.

There are a lot of options in any case, it'll be interesting to see what you pick!
fachat
Posts: 1123
Joined: 05 Jul 2005
Location: near Heidelberg, Germany
Contact:

Re: VGA Work

Post by fachat »

gfoot wrote:
Quote:
My next goal will be to adjust some of the counter outputs so the memory laid out in one continuous line to make the math on the 6502 easier; I will also be adding 4-bit color as well. The memory layout will be the same as text mode on the IBM PCs, as that is what I'm familiar with.
This can be more complicated than you might think when you are doing scanline-doubling or text-mode output, as you need to scan the same range of memory addresses multiple times on a range of scanlines - when you come to each subsequent scanline, you'll need to reset the address counters to the values they had at the start of the previous scanline, which is not going to be a nice round number. I've thought of a few ways to do this in the past (including of course just storing the right value and initialising the counters to that value on subsequent scanlines) but so far I have backed out from actually doing it because of the added complexity. Instead I embrace the possibility of having some room for horizontal scrolling, or failing that, just spare RAM for other purposes!
Resetting the address counter is something the old machines like the PET have done in their original video circuit. And the principle is actually simple - latch the address at/after the last character of the previous charline and reset to that value at the end of a rasterline - except the last rasterline of the charline.

I have done an extensive analysis on my Youtube channel https://youtube.com/@8bittimes

André
Author of the GeckOS multitasking operating system, the usb65 stack, designer of the Micro-PET and many more 6502 content: http://6502.org/users/andre/
plasmo
Posts: 1273
Joined: 21 Dec 2018
Location: Albuquerque NM USA

Re: VGA Work

Post by plasmo »

Yuri wrote:
Or Dual-Port RAM.... but where's the fun in that? (Famous last words)
Dual port for video data is certainly an easier solution. You can read and write any time leisurely and the CPLD can be small and cheap. The problem is cost of dual port RAM that limits the resolution/color of display. 4KB dual port RAM is inexpensive and good for mono text display, but color graphic displays require unaffordable large dual port RAM. You can build dual port RAM from regular RAM, but that needs CPLD with large number of I/O and more macrocells. It is a tradeoff.
Bill
Post Reply