Author Topic: FPGA VGA Controller for 8-bit computer  (Read 510784 times)

0 Members and 52 Guests are viewing this topic.

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #225 on: November 08, 2019, 03:14:52 am »
EDIT:

Yes, that sorted it.  I've only used two outputs per sync signal for the moment, but it's working fine inverted now with no misbehaviour.  :-+
Ok, if 1 x 74AC/HC CMOS buffer was on the line when using negative sync to drive the monitor cables, and 2 in parallel work with any sync polarity, can you see why I said 2 pages ago, 'USE 3 CMOS BUFFERS in parallel....'  Your monitor appears to have a 75 Ohm termination resistor on it's sync lines and they might be using a schmitt trigger meaning that the drive would need to clear almost 4v by the time it reaches the monitors internal circuitry...  If you are now using 2, go to 3, otherwise, on a warm day, when your 74AC/HC CMOS buffer warms up, and it's drive switch resistance drops a bit, of your 5v power supply may be a still valid 4.75v, you will wonder which your monitor's picture has begun to get interference.  Your other choice is to use a single line driver buffer designed to drive a 25ohm load, or a transistor buffer.  With one of those, the sync signal will reach your monitor with the full 0-5v swing.
« Last Edit: November 08, 2019, 03:16:25 am by BrianHG »
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #226 on: November 08, 2019, 03:26:03 am »
Operating like this, if you have a variable HDE and VDE window anywhere in your raster at any size, the X and Y counters will be at their next ready valid numerical state immediately after each line has ended and the frame has ended.

Fantastic - works beautifully and is quite small and neat, a great combination.  ;D

(Attachment Link)   (Attachment Link)

One thing I've noticed - there's more colour bars... Is the x counter incrementing properly?

Show me your code.  You may have your X&Y counter outside the:
-----------------------------------------------------------------
         if (pc_ena)   // once per pixel
         begin
-----------------------------------------------------------------
Meaning it may be running at 50MHz instead of 25MHz.
 

Offline jhpadjustable

  • Frequent Contributor
  • **
  • Posts: 295
  • Country: us
  • Salt 'n' pepper beard
Re: FPGA VGA Controller for 8-bit computer
« Reply #227 on: November 08, 2019, 04:40:43 am »
This should read a file in the project (borrowed from BrianHG's altera_osd_20kbit files) which contains the character definitions into memory.  Except I don't think it's loading it into memory - I think it's loading into hardware, so it's creating a 'ROM' in the sense that it can't be written to, but I get the feeling it's an extremely wasteful way of doing it.   Please correct me if I'm wrong. Isn't there a megafunction or wizard of some kind I need to use to create a RAM/ROM area, or does Quartus do this for me by inferring my intentions from the code?
You can use the generic HDL to be portable, or you can instantiate and configure vendor modules to get vendor features, or you can use the megawizard to configure the vendor modules to get vendor features and pointy-clicky satisfaction. Mostly, it's a style matter. Quartus will indeed infer your block RAM intentions if your code walks, talks, and quacks enough like a duck. If not, you can use a ramstyle pragma to specify the style of RAM you want to use. https://www.intel.com/content/www/us/en/programmable/quartushelp/current/index.htm#hdl/vlog/vlog_file_dir_ram.htm

That ROM_font module has two big problems: one, it is asynchronous. From p15 of the coding style handbook: "Because memory blocks in the newest devices from Altera are synchronous, RAM designs that are targeted towards architectures that contain these dedicated memory blocks must be synchronous to be mapped directly into the device architecture. For these devices, asynchronous memory logic is implemented in regular logic cells." Two, it is sized incorrectly. Specify memory sizes in words rather than bits, much as you would a two-dimensional array, but mind the off-by-one error. 256 tiles of 16 rows each would be sized as reg [n:0] mymemory [0:(256*16)-1] regardless of the register width n.

Quote
Yes, I could read a file into memory like with the font file above. I thought the RAM would contain random values, so I wouldn't have to load anything initially to see something come out on the screen... is that not the case then?  Is RAM pre-initialised to zeros or something?

I think it's customary that block RAMs are initialized to zero upon power-up unless otherwise specified. In simulation, block RAMs are usually initialized to X so that you can see when you're reading from memory that you haven't written to (whether initially or in your logic) and trace how far the consequences reach.

Quote
Would it be better to return a byte (an entire row from the selected character) and bit-select from that returned value, or configure the 'ROM' to just return the bit in question pointed to by x & y?  What's the difference in terms of the 'ROM' code?  Is it as simple as:
I suggest byte-wide character bitmap "ROM" and external bit-selection with a look toward the future. If you decide to make the character bitmap block writable by the host, synthesis won't have to try to infer a dual-port RAM with dissimilar widths on each port, and won't have to guess/decide a possibly incorrect bit-endianness, because you will have specified it explicitly in logic. Whether you bit-select inside or outside the ROM_font module is a matter of style. Personally, I'd treat the x8 ROM as a x8 ROM all round and do the assign bit_out = rom_font_data[~xpos[2:0]]; outside of the ROM_font module, in case you might eventually reuse the bit selector for the frame buffer. That said, I've not done HDL projects too much larger than this, so FPGA old hands feel free to roast me if I'm doing it wrong.  :popcorn:
"There are more things in heaven and earth, Arduino, than are dreamt of in your philosophy."
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #228 on: November 08, 2019, 05:02:38 am »
Ok, I've cleaned up my OSD generator, commented on everything and it gives a much better starting point.
We will make changes to make things better and touch up the font into standard 8 bit bytes, but as an introduction, this shows how the basics of how one of Intel's memory function is called and configured.

Code: [Select]
module osd_generator ( clk, pc_ena, hs_in, vs_in, osd_ena_out, osd_image, hde_out, vde_out, hs_out, vs_out,
wren_disp, wren_font, wr_addr, wr_data );

// To write contents into the display and font memories, the wr_addr[15:0] selects the address
// the wr_data[7:0] contains a byte which will be written
// the wren_disp is the write enable for the ascii text ram.  Only the wr_addr[8:0] are used as the character display is 32x16.
// the wren_font is the write enable for the font memort.  Only 2 bits are use of the wr_dataa[1:0] and wr_addr[12:0] are used.
// tie these ports to GND for now disabling them

input  clk, pc_ena, hde_in, vde_in, hs_in, vs_in ;

output osd_ena_out ;
reg    osd_ena_out ;
output [2:0] osd_image ;
output hde_out, vde_out, hs_out, vs_out;


input wren_disp, wren_font;
input [15:0] wr_addr;
input [7:0] wr_data;

reg   [9:0] disp_x,dly1_disp_x,dly2_disp_x;
reg   [8:0] disp_y,dly1_disp_y,dly2_disp_y;

reg   dena,dly1_dena,dly2_dena,dly3_dena,dly4_dena;
reg   [7:0] dly1_letter, dly2_letter;

reg   [7:0] hde_pipe, vde_pipe, hs_pipe, vs_pipe;


wire [12:0] font_pos;
wire [8:0]  disp_pos;
wire [2:0]  osd_image;


parameter   PIPE_DELAY =  4;   // This parameter selects the number of pixel clocks which the output VDE and syncs are delayed.  Only use 2 through 9.

// ****************************************************************************************************************************
// LPM_RAM_DP Quartus dual port memory function
// ****************************************************************************************************************************
wire [7:0] sub_wire0;
wire [7:0] letter = sub_wire0[7:0];
// w access = 'b1100000a aaaaaaaa
altsyncram altsyncram_component_osd_mem ( .wren_a ( wren_disp ), .clock0 (clk), .clock1 (clk), .clocken1 (pc_ena),
.address_a (wr_addr[8:0]), .address_b (disp_pos[8:0]),
.data_a (wr_data[7:0]), .q_b (sub_wire0));


defparam
altsyncram_component_osd_mem.intended_device_family = "Cyclone",
altsyncram_component_osd_mem.operation_mode = "DUAL_PORT",
altsyncram_component_osd_mem.width_a = 8,
altsyncram_component_osd_mem.widthad_a = 9,
altsyncram_component_osd_mem.width_b = 8,
altsyncram_component_osd_mem.widthad_b = 9,
altsyncram_component_osd_mem.lpm_type = "altsyncram",
altsyncram_component_osd_mem.width_byteena_a = 1,
altsyncram_component_osd_mem.outdata_reg_b = "CLOCK1",
altsyncram_component_osd_mem.indata_aclr_a = "NONE",
altsyncram_component_osd_mem.wrcontrol_aclr_a = "NONE",
altsyncram_component_osd_mem.address_aclr_a = "NONE",
altsyncram_component_osd_mem.address_reg_b = "CLOCK1",
altsyncram_component_osd_mem.address_aclr_b = "NONE",
altsyncram_component_osd_mem.outdata_aclr_b = "NONE",
altsyncram_component_osd_mem.ram_block_type = "AUTO",
altsyncram_component_osd_mem.init_file = "osd_mem.mif";

// ****************************************************************************************************************************

wire [1:0] sub_wire1;
wire [1:0] osd_img = sub_wire1[1:0];
// w access = 'b111aaaaa aaaaaaaa
altsyncram altsyncram_component_osd_font ( .wren_a ( wren_font ), .clock0 (clk), .clock1 (clk), .clocken1 (pc_ena),
.address_a (wr_addr[12:0]), .address_b (font_pos[12:0]),
.data_a (wr_data[1:0]), .q_b (sub_wire1));
defparam
altsyncram_component_osd_font.intended_device_family = "Cyclone",
altsyncram_component_osd_font.operation_mode = "DUAL_PORT",
altsyncram_component_osd_font.width_a = 2,
altsyncram_component_osd_font.widthad_a = 13,
altsyncram_component_osd_font.width_b = 2,
altsyncram_component_osd_font.widthad_b = 13,
altsyncram_component_osd_font.lpm_type = "altsyncram",
altsyncram_component_osd_font.width_byteena_a = 1,
altsyncram_component_osd_font.outdata_reg_b = "CLOCK1",
altsyncram_component_osd_font.indata_aclr_a = "NONE",
altsyncram_component_osd_font.wrcontrol_aclr_a = "NONE",
altsyncram_component_osd_font.address_aclr_a = "NONE",
altsyncram_component_osd_font.address_reg_b = "CLOCK1",
altsyncram_component_osd_font.address_aclr_b = "NONE",
altsyncram_component_osd_font.outdata_aclr_b = "NONE",
altsyncram_component_osd_font.ram_block_type = "AUTO",
altsyncram_component_osd_font.init_file = "osd_font.mif";

// ****************************************************************************************************************************
// ****************************************************************************************************************************


//  The disp_x is the X coordinate counter.  It runs from 0 to 512 and stops there
//  The disp_y is the Y coordinate sounter.  It runs from 0 to 256 and stops there

assign disp_pos[4:0]  = disp_x[8:4] ;  // The disp_pos[4:0] is the lower address for the 32 characters for the ascii text.
assign disp_pos[8:5]  = disp_y[7:4] ;  // the disp_pos[8:5] is the upper address for the 16 lines of text


//  The result from the ascii memory component 'altsyncram_component_osd_mem'  is called letter[7:0]
//  Since disp_pos[8:0] has entered the read address, it takes 2 pixel clock cycles for the resulting letter[7:0] to come out.

//  Now, font_pos[12:0] is the read address for the memory block containing the font memory

assign font_pos[12:6] = letter[6:0] ;       //  Select the upper font address with the 7 bit letter, note the atari font has only 128 characters.
assign font_pos[2:0]  = dly2_disp_x[3:1] ;  //  select the font X coordinate with a 2 pixel clock DELAYES disp_x address.  [3:1] is used so that every 2 x pixels are repeats
assign font_pos[5:3]  = dly2_disp_y[3:1] ;  //  select the font y coordinate with a 2 pixel clock DELAYES disp_y address.  [3:1] is used so that every 2 y lines are repeats


//  The resulting font image, 2 bits since I made a 2 bit color atari font is assigned to the OSD[1:0] output
//  Also, since there is an 8th bit in the ascii text memory, I use that as a third OSD[2] output color bit.

assign osd_image[1:0] = osd_img[1:0];
assign osd_image[2]   = dly2_letter[7];  // Remember, it takes 2 pixel clocks for osd_img[1:0] data to be valid from read address letter[6:0]
                                         // so, if we want to use letter[7] as an upper color bit, is needs to be delayed 2 pixel clocks so it will be parallel with the osd_img[1:0] read data

always @ ( posedge clk ) begin

if (pc_ena) begin

// **************************************************************************************************************************
// *** Create a serial pipe where the PIPE_DELAY parameter selects the pixel count delay for the xxx_in to the xxx_out ports
// **************************************************************************************************************************

hde_pipe[0]   <= hde_in;
hde_pipe[7:1] <= hde_pipe[6:0];
hde_out       <= hde_pipe[PIPE_DELAY-2];

vde_pipe[0]   <= vde_in;
vde_pipe[7:1] <= vde_pipe[6:0];
vde_out       <= vde_pipe[PIPE_DELAY-2];

hs_pipe[0]    <= hs_in;
hs_pipe[7:1]  <= hs_pipe[6:0];
hs_out        <= hs_pipe[PIPE_DELAY-2];

vs_pipe[0]    <= vs_in;
vs_pipe[7:1]  <= vs_pipe[6:0];
vs_out        <= vs_pipe[PIPE_DELAY-2];

// **********************************************************************************************


// This OSD generator's window is only 512 pixels by 256 lines.
// Since the disp_X&Y counters are the screens X&Y coordinates,
// I'm using an extra most significant bit in the counters to determine if the
// OSD ena flag should be on or off


if (disp_x[9] || disp_y[8]) dena <= 0;  // When disp_x > 511 or disp_y > 255, then turn off the OSD's output enable flag
else dena <= 1;                         // otherwise, turn on the OSD output enable flag.


if ( ~vde ) disp_y[8:0] <= 9'b111111111;  // preset the disp_y counter to max while the vertical display is disabled

  else if (hde_in && ~hde_pipe[0]) begin      // isolate a single event at the begining of the active display area

disp_x[9:0] <= 10'b0000000000; // clear the disp_x counter

if (!disp_y[8] | (disp_y[8:7] == 2'b11)) disp_y <= disp_y + 1;  //  only increment the disp_y counter if it hasn't reached it's end

end else if (!disp_x[9]) disp_x <= disp_x + 1;  // keep on addind to the disp_x counter until it reaches it's end.


// **********************************************************************************************
// *** These delay pipes registers are explaines in the 'assign's above.
// **********************************************************************************************

dly1_disp_x <= disp_x;
dly2_disp_x <= dly1_disp_x;

dly1_disp_y <= disp_y;
dly2_disp_y <= dly1_disp_y;

dly1_letter <= letter;
dly2_letter <= dly1_letter;

dly1_dena   <= dena;
dly2_dena   <= dly1_dena;
dly3_dena   <= dly2_dena;
dly4_dena   <= dly3_dena;

// **********************************************************************************************

osd_ena_out  <= dly2_dena; // This is used to drive a graphics A/B switch which tells when the OSD graphics should be shown
                           // It needs to be delayes by the number of pixel clocks required for the above memories


end // ena
end // always@clk
endmodule


Before we make changes like addressing 80 columns, addressing anywhere withing a chunk of ram, I'll fetch out my palette and overlay generator which goes with this OSD generator.  This will allow you to simultaneously run your pattern generator and superimpose the OSD generator on top, with a semi transparent layer in the font and a palette for the font colors.  For now, you can just wire the 'OSD[2:0]' output's bits 0 to your r&g&b outputs.

Hint, in the block diagram editor, double click on a blank area and type 'WIRE'.  Place these on your schematic and label on the left the buss name and number of my OSD outputs and on the right place a wire and label your RGB pins names.

Also, for the write memory inputs, on the block diagram editor, double click on a blank area, and type GND, and insert 3 GND symbols and tie them to the inputs on the OSD generator module.

The osd text memory block .mif should contain a display font test text of all the characters.  You and edit the file in quartus to make it say anything you like.


Note: After the palette and we will tackle a true memory address generator which will allow text windows of any Z-size by any Y-size, pointing to any memory base address, with a hidden overscan or any text columns width.  Plus, variable X&Y font size and 16 foreground by 16 background colors, paletted and programable for each letter on the display.  We will also be embedding the font memory into the same memory as the ascii text ram which will also be you main 8bit cpu ram as well.  There should be no problem implementing the 256 color graphics as well, but, the resolution window will be puny and we might need to forgo it's palette.

ALSO: I need to you increase your output pixel bits to 4 for each R,G,B, that's 12 bit color, or,  4096 colors.  Do not erase your pattern generator, we will be still bringing it back soon for additional tests.
« Last Edit: November 08, 2019, 08:31:35 am by BrianHG »
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #229 on: November 08, 2019, 07:36:10 am »
I suggest byte-wide character bitmap "ROM" and external bit-selection with a look toward the future. If you decide to make the character bitmap block writable by the host, synthesis won't have to try to infer a dual-port RAM with dissimilar widths on each port, and won't have to guess/decide a possibly incorrect bit-endianness, because you will have specified it explicitly in logic. Whether you bit-select inside or outside the ROM_font module is a matter of style. Personally, I'd treat the x8 ROM as a x8 ROM all round and do the assign bit_out = rom_font_data[~xpos[2:0]]; outside of the ROM_font module, in case you might eventually reuse the bit selector for the frame buffer. That said, I've not done HDL projects too much larger than this, so FPGA old hands feel free to roast me if I'm doing it wrong.  :popcorn:

Yes, I'm going there.... I just want the OP to take a few steps inbetween since we will soon be having either an 8bit or 16bit wide dual port ram which will be sized to the maximum the FPGA can handle (parameter configurable so larger FPGA will also work), and the display text, font, text color, and a universal graphics mode will have a base pointer in that same ram which is loaded during the v-sync.  A string of bytes from that base pointer will be loaded into control registers containing all the graphics control metrics, updated every h-sync, making a fully programable display pretty much on par with a Super Nintendo, except with support for 640x480 in the text mode and 4 color bitplane graphics mode.  These functions, really just 2 counters with a variable increment at the end of each line, will include text rows/video modes with a programmable width which may be wider than the entire display screen.

First we needed enough to get his project running the pixel clock at 4x, 100MHz, yet with a 25MHz pixel out while verifying the characters and background graphics were scaling correctly without error & his graphics rendering pipe was functional.

In the future, if the OP wishes to upgrade to external DRAM, that 1 memory block will need to be converted into a cache memory for the external ram and the constructed image will need pre-fetching a line of video in advance.  This one gets nasty.
« Last Edit: November 08, 2019, 08:26:15 am by BrianHG »
 
The following users thanked this post: jhpadjustable

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #230 on: November 08, 2019, 10:21:35 am »
Ok, if 1 x 74AC/HC CMOS buffer was on the line when using negative sync to drive the monitor cables, and 2 in parallel work with any sync polarity, can you see why I said 2 pages ago, 'USE 3 CMOS BUFFERS in parallel....'  Your monitor appears to have a 75 Ohm termination resistor on it's sync lines and they might be using a schmitt trigger meaning that the drive would need to clear almost 4v by the time it reaches the monitors internal circuitry...  If you are now using 2, go to 3, otherwise, on a warm day, when your 74AC/HC CMOS buffer warms up, and it's drive switch resistance drops a bit, of your 5v power supply may be a still valid 4.75v, you will wonder which your monitor's picture has begun to get interference.  Your other choice is to use a single line driver buffer designed to drive a 25ohm load, or a transistor buffer.  With one of those, the sync signal will reach your monitor with the full 0-5v swing.

I'm not deliberately ignoring advice, just trying to make do with the bits I have to hand - namely, a shortage of very short linking wires for the breadboard the sync driver is plugged into.  ::)  As usual for a beginner, I made do with two parallel outputs, found it worked and stopped there.  Have upped my game now and got three parallel outputs driving each sync signal now.  :-+

I've also got a bit of a loose connection somewhere... the fuzzy column edges and blinking came back earlier, but I suspect a bad connection on the breadboard around the driver output as some finger poking around that area sorted it.  :-/O  Can't wait for the EP4C dev board to arrive - that has a built-in VGA port and will help me focus entirely on the HDL development.

One thing I've noticed - there's more colour bars... Is the x counter incrementing properly?

Show me your code.  You may have your X&Y counter outside the:
-----------------------------------------------------------------
         if (pc_ena)   // once per pixel
         begin
-----------------------------------------------------------------
Meaning it may be running at 50MHz instead of 25MHz.

Ah yes, the x and y counters were outside the if (p_ena) conditional.  :palm:  I'll attach my code at the bottom of this post anyway, for the sake of having an archive of it if nothing else.

Speaking of which, I've got a string of folders (currently on number 4) for this project, at various stages.  I think I might just run with this current project now and get it onto github.  I had an accident earlier where I deleted a file I thought I didn't need anymore, and it turned out to be the current sync_generator file.  Had to go back to a previous post and use the cleaned-up code you'd posted to re-create the file again.  Repo is up on github if you want the latest files.

You can use the generic HDL to be portable, or you can instantiate and configure vendor modules to get vendor features, or you can use the megawizard to configure the vendor modules to get vendor features and pointy-clicky satisfaction. Mostly, it's a style matter. Quartus will indeed infer your block RAM intentions if your code walks, talks, and quacks enough like a duck. If not, you can use a ramstyle pragma to specify the style of RAM you want to use.

No problemo, will stick to generic HDL as much as possible then - I was just getting confused between the output from a wizard and (what appears to me to be) very basic HDL code to create a memory block.

That ROM_font module has two big problems: one, it is asynchronous... Two, it is sized incorrectly. Specify memory sizes in words rather than bits, much as you would a two-dimensional array, but mind the off-by-one error. 256 tiles of 16 rows each would be sized as reg [n:0] mymemory [0:(256*16)-1] regardless of the register width n.

Ah okay, I should go read those documents a little more closely.  I suspect this is a moot point anyway as BrianHG is clearly working to a plan and this will get covered soon.

Ok, I've cleaned up my OSD generator, commented on everything and it gives a much better starting point.
We will make changes to make things better and touch up the font into standard 8 bit bytes, but as an introduction, this shows how the basics of how one of Intel's memory function is called and configured.

Thanks again BrianHG - I'll spend some time looking at this today.

Before we make changes like addressing 80 columns, addressing anywhere withing a chunk of ram, I'll fetch out my palette and overlay generator which goes with this OSD generator.  This will allow you to simultaneously run your pattern generator and superimpose the OSD generator on top, with a semi transparent layer in the font and a palette for the font colors.  For now, you can just wire the 'OSD[2:0]' output's bits 0 to your r&g&b outputs.

Righto, no problem - so I should be able to get this working at 32x16 as it is...  :o

Note: After the palette and we will tackle a true memory address generator which will allow text windows of any Z-size by any Y-size, pointing to any memory base address, with a hidden overscan or any text columns width.  Plus, variable X&Y font size and 16 foreground by 16 background colors, paletted and programable for each letter on the display.  We will also be embedding the font memory into the same memory as the ascii text ram which will also be you main 8bit cpu ram as well.  There should be no problem implementing the 256 color graphics as well, but, the resolution window will be puny and we might need to forgo it's palette.

Aye carumba! This is going to be MUCH better than the plain old 'white on black' text mode, maybe with a splash of colour, that I was thinking of...  ;D

Okay, off to go work on this...
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #231 on: November 08, 2019, 11:30:28 am »
Now, for a little more pushing... Once you have integrated my latest OSD generator, and get it working, the next step will be to modify the current 2 bit , 2 color per pixel font memory into a 1 bit color, 8 bit wide font memory.  I've attached a new Atari font which is formatted the way @jhpadjustable likes it.  This means you need to adjust the read addresses, size and location, and at the 8 bit data output, you need to make an immediate 8:1 mux selector to draw the 1bit font image.

The new font files I've attached are in 2 formats.
#1, "osd_font_8x8bit_128char.mif", A quartus .mif format file which is internally used for it's built in LPM_RAM_DP / AltSyncram function.
#2, "osd_font_8x8bit_128char.rom" A raw binary file which is a raw 8 bit binary dump of the Atari font.  Quartus can also use these, however 10 years ago, the feature had bugs so I convert the binaries to .mif and use them that way in quartus.

You will need to change the AltSyncram function I used in the OSD generator.  Read up on Quartus' library function for that memories controls as you need to change everything to 8bit data.  This step will allow us to combine both the character and font memory into 1 large continuous ram block for your Z80 to access.

Show me first the current character generator working, then you will need to modify the font memory into 8bit wide, monochrome color mode and verify that is also working properly.


---------------------------------------------------------------------------------
Your next step after this will be both the merge and making the new ram block have 5 dedicated read ports running at the 25MHz pixel clock speed.  This means the ram is running at 125MHz and you have a 5 muxed address in and 5 muxed/latched datas out while the second port on the ram will now be a read/write port which will be dedicated to the Z80 and way int the future, perhaps some graphics acceleration functions.

------------------------------------------------------------------------------
VERY Important, each step of the way, keep track of your FMAX!!!  If a piece of code you generate along the way kills it below 150Mhz approximately, find out why and ask questions.  Because, if you go too far with a 75MHz FMAX, it may be tedious to fix the problem lateron.
« Last Edit: November 08, 2019, 11:56:47 am by BrianHG »
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #232 on: November 08, 2019, 11:43:34 am »

Aye carumba! This is going to be MUCH better than the plain old 'white on black' text mode, maybe with a splash of colour, that I was thinking of...  ;D

Okay, off to go work on this...
Nope, even with your current FPGA, you will achieve a font display with 16 foreground + 16 background colors, with 4096 paletted colors and 16 translucency levels! as your text mode will be superimposed on top of the bitplane and 256 color graphics mode which also have a programmable 4096 colors for each of those 256 paletted colors.  Yes, the letters color palette will set different levels of translucency for both the font's foreground and background.  This means 16 stage fading just by manipulating the font's palette.  All this with 80 column x 60 rows, all within 16kb.  Wait a sec, does your FPGA have 16kb?  If not, oh well, we'll use 40x30 mode for now and you will only need 4kb for now.

Each line of text will have X&Y pixel sizes of 1,2,3,4 pixel/line.  Basically a separate X&Y scale size.  Same goes for the bitplane and 256 color graphics modes.  And yes, the graphics have their own separate palette too....
« Last Edit: November 08, 2019, 11:58:23 am by BrianHG »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #233 on: November 08, 2019, 12:09:39 pm »
@BrianHG

Just had to make a couple of corrections to osd_generator to get it to successfully compile a symbol file:

1) hde_in, vde_in added to ports in first line
2) Declared hde_out, vde_out, hs_out and vs_out as regs as they're given blocking assignments in an always in lines 130 onwards
3) Line 160 - if ( ~vde ) - changed to if ( ~vde_in )

For now, you can just wire the 'OSD[2:0]' output's bits 0 to your r&g&b outputs.

Hint, in the block diagram editor, double click on a blank area and type 'WIRE'.  Place these on your schematic and label on the left the buss name and number of my OSD outputs and on the right place a wire and label your RGB pins names.

I think I've done what you've asked correctly...  ???

The osd text memory block .mif should contain a display font test text of all the characters.  You and edit the file in quartus to make it say anything you like.

But osd_mem.mif should show something, right?  Should show all the ASCII chars on the screen, in theory?  I'm just getting a blank screen - no text at all, all black (at least it's not going into standby)?

ALSO: I need to you increase your output pixel bits to 4 for each R,G,B, that's 12 bit color, or,  4096 colors.  Do not erase your pattern generator, we will be still bringing it back soon for additional tests.

Ah, will this cause the issue above then?  I haven't done it yet - my output is restricted to 1-bit colour at the moment as I'm still using the 220R resistors in series with the R, G and B outputs.

Here's my current setup:

868512-0
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #234 on: November 08, 2019, 12:21:42 pm »
You forgot to add a buss wires on the:
osd_image[2..0] output, and on that bus, write the label 'osd_image[2..0]'

Quartus will not associate the labels on your wires unless you tie them to a block diagram's IO somewhere with the same name.  It does this since you may name these buss wires anything, not necessarily the internal verilog names.

As for the rgb_in[1], again, you need to add a bus wires on the stencil's input and add rgb_in[1..0]'
Add 3 more wire symbols after the rgb_in[1] wires and add the rgb_in[0] wires shorting the 2 together to generate a full brightness image. Or, you can wire the 'osd_ena_out' into all 3 'rgb_in[0]' instead.  This will generate a differenct luminance located where the the OSD character generator is active.

ALSO, use a different osd_image[ # ] for each red,green,blue.
« Last Edit: November 08, 2019, 12:28:24 pm by BrianHG »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #235 on: November 08, 2019, 12:50:25 pm »
Oh wow - yes, got an output now:

868524-0

Not sure it's aligned correctly - perhaps needs to move right and down by 3 pixels in each axis?

I'm guessing the vertical bars are because the 32 vertical lines have been exceeded?

You forgot to add a buss wires on the:
osd_image[2..0] output, and on that bus, write the label 'osd_image[2..0]'

D'oh.  I thought Quartus would link the wires on the Wire to the correct block IO, but I guess several blocks could all have the same IO names...

Add 3 more wire symbols after the rgb_in[1] wires and add the rgb_in[0] wires shorting the 2 together to generate a full brightness image. Or, you can wire the 'osd_ena_out' into all 3 'rgb_in[0]' instead.  This will generate a differenct luminance located where the the OSD character generator is active.

The outputs for r[0]. g[0] and b[0] aren't connected to anything yet.  I'll need to set up a resistor ladder to make use of those outputs.  I'll put that on my to-do list.  ;)

ALSO, use a different osd_image[ # ] for each red,green,blue.

Spot on.  The first time I ran it on the FPGA I just got a white screen.   :-+
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #236 on: November 08, 2019, 01:01:31 pm »
To fix the OSD outline, change the first 3 'WIRE' symbols you have to 2 INPUT AND gates.  Wire 1 input of each and gate to exactly what you have now, and, wire the second of each and gate's input to the OSD generators 'osd_ena_out' signal.

This will mute the OSD output's image when outside the active 32x16 box.

Also, I think your monitor might need the H and V size adjusted.  I thought my OSD generator had perfect alignment.
(This wouldn't be a problem with DVI...)

This originally was a superimposed window as a debug screen for my video enhancer project.
« Last Edit: November 08, 2019, 01:05:06 pm by BrianHG »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #237 on: November 08, 2019, 01:34:18 pm »
To fix the OSD outline, change the first 3 'WIRE' symbols you have to 2 INPUT AND gates.  Wire 1 input of each and gate to exactly what you have now, and, wire the second of each and gate's input to the OSD generators 'osd_ena_out' signal.

This will mute the OSD output's image when outside the active 32x16 box.

Looking better already.  ;D

868544-0

Also, I think your monitor might need the H and V size adjusted.  I thought my OSD generator had perfect alignment.
(This wouldn't be a problem with DVI...)

 :palm:

Of course.. Auto-adjust fixed it straight away. There I was going straight for the complex problems, thinking it was a timing problem...  :-DD

Right, I've put together a 2-bit resistor ladder, so I now have 2-bit RGB output. 
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #238 on: November 08, 2019, 01:52:46 pm »
LOL.... Live and learn...

Anyways, the next step.  Now you will need to understand my OSD code.  You will recognize that except for the way I made the 2bit font, it pretty much operates as you figured all along except for those piped delays to match after the first ram's read's contents are ready, the next one being the font image lookup, has it's x&y pointers delayed so they come in parallel with the contents of the text memory.

Now, I want you to change my font ram weird 2 bit color into an 8 bit wide font, now only B&W.  Use the new 8bit wide font I posted 7 messages up.  Try to get it to work properly.  It should fit in easily, though, you will need to mux select 8:1 the output at the right position.  Your output image should now be 2 bit color.  1 bit B&W for the font, and 1 bit for the MSB, bit 7, in the character memory.  Let's see if you can keep everything perfectly pixel aligned.

Also take note of your current FMAX...

Also, copy the osd.c module into a new name and work with that one in the same project so you can swap modules to verify alignment.

« Last Edit: November 08, 2019, 01:57:56 pm by BrianHG »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #239 on: November 08, 2019, 05:17:24 pm »
Going to take this slowly, as that's how my mind works..  :o ;D

Now, I want you to change my font ram weird 2 bit color into an 8 bit wide font, now only B&W.  Use the new 8bit wide font I posted 7 messages up.

So here's my first attempt:

Code: [Select]
module vid_osd_generator ( clk, pc_ena, hde_in, vde_in, hs_in, vs_in, osd_ena_out, osd_image, hde_out, vde_out, hs_out, vs_out,
wren_disp, wren_font, wr_addr, wr_data );

// To write contents into the display and font memories, the wr_addr[15:0] selects the address
// the wr_data[7:0] contains a byte which will be written
// the wren_disp is the write enable for the ascii text ram.  Only the wr_addr[8:0] are used as the character display is 32x16.
// the wren_font is the write enable for the font memory.  Only 2 bits are used of the wr_data[1:0] and wr_addr[12:0] are used.
// tie these ports to GND for now disabling them

input  clk, pc_ena, hde_in, vde_in, hs_in, vs_in;

output osd_ena_out;
reg    osd_ena_out;
output [2:0] osd_image;
output hde_out, vde_out, hs_out, vs_out;
reg hde_out, vde_out, hs_out, vs_out;

input wren_disp, wren_font;
input [15:0] wr_addr;
input [7:0] wr_data;

reg   [9:0] disp_x,dly1_disp_x,dly2_disp_x;
reg   [8:0] disp_y,dly1_disp_y,dly2_disp_y;

reg   dena,dly1_dena,dly2_dena,dly3_dena,dly4_dena;
reg   [7:0] dly1_letter, dly2_letter;

reg   [7:0] hde_pipe, vde_pipe, hs_pipe, vs_pipe;

wire [9:0] font_pos;
wire [8:0]  disp_pos;
wire [2:0]  osd_image;

parameter   PIPE_DELAY =  4;   // This parameter selects the number of pixel clocks to delay the VDE and sync outputs.  Only use 2 through 9.

// ****************************************************************************************************************************
// SCREEN TEXT MEMORY
// ****************************************************************************************************************************
wire [7:0] sub_wire0;
wire [7:0] letter = sub_wire0[7:0];
// w access = 'b1100000a aaaaaaaa
altsyncram altsyncram_component_osd_mem ( .wren_a ( wren_disp ), .clock0 (clk), .clock1 (clk), .clocken1 (pc_ena),
.address_a (wr_addr[8:0]), .address_b (disp_pos[8:0]),
.data_a (wr_data[7:0]), .q_b (sub_wire0));

defparam
altsyncram_component_osd_mem.intended_device_family = "Cyclone II",
altsyncram_component_osd_mem.operation_mode = "DUAL_PORT",
altsyncram_component_osd_mem.width_a = 8,
altsyncram_component_osd_mem.widthad_a = 9,
altsyncram_component_osd_mem.width_b = 8,
altsyncram_component_osd_mem.widthad_b = 9,
altsyncram_component_osd_mem.lpm_type = "altsyncram",
altsyncram_component_osd_mem.width_byteena_a = 1,
altsyncram_component_osd_mem.outdata_reg_b = "CLOCK1",
altsyncram_component_osd_mem.indata_aclr_a = "NONE",
altsyncram_component_osd_mem.wrcontrol_aclr_a = "NONE",
altsyncram_component_osd_mem.address_aclr_a = "NONE",
altsyncram_component_osd_mem.address_reg_b = "CLOCK1",
altsyncram_component_osd_mem.address_aclr_b = "NONE",
altsyncram_component_osd_mem.outdata_aclr_b = "NONE",
altsyncram_component_osd_mem.ram_block_type = "AUTO",
altsyncram_component_osd_mem.init_file = "osd_mem.mif";

// ****************************************************************************************************************************
// FONT MEMORY
// ****************************************************************************************************************************
wire [7:0] sub_wire1;
wire [7:0] char_line = sub_wire1;
// w access = 'b111aaaaa aaaaaaaa
altsyncram altsyncram_component_osd_font ( .wren_a ( wren_font ), .clock0 (clk), .clock1 (clk), .clocken1 (pc_ena),
.address_a (wr_addr[12:0]), .address_b (font_pos[9:0]),
.data_a (wr_data[7:0]), .q_b (sub_wire1));
defparam
altsyncram_component_osd_font.intended_device_family = "Cyclone II",
altsyncram_component_osd_font.operation_mode = "DUAL_PORT",
altsyncram_component_osd_font.width_a = 8,
altsyncram_component_osd_font.widthad_a = 13,
altsyncram_component_osd_font.width_b = 8,
altsyncram_component_osd_font.widthad_b = 10,
altsyncram_component_osd_font.lpm_type = "altsyncram",
altsyncram_component_osd_font.width_byteena_a = 1,
altsyncram_component_osd_font.outdata_reg_b = "CLOCK1",
altsyncram_component_osd_font.indata_aclr_a = "NONE",
altsyncram_component_osd_font.wrcontrol_aclr_a = "NONE",
altsyncram_component_osd_font.address_aclr_a = "NONE",
altsyncram_component_osd_font.address_reg_b = "CLOCK1",
altsyncram_component_osd_font.address_aclr_b = "NONE",
altsyncram_component_osd_font.outdata_aclr_b = "NONE",
altsyncram_component_osd_font.ram_block_type = "AUTO",
altsyncram_component_osd_font.init_file = "osd_font_8x8bit_128char.mif";

// ****************************************************************************************************************************
// ****************************************************************************************************************************


//  The disp_x is the X coordinate counter.  It runs from 0 to 512 and stops there
//  The disp_y is the Y coordinate counter.  It runs from 0 to 256 and stops there

// Get the character at the current x, y position
assign disp_pos[4:0]  = disp_x[8:4] ;  // The disp_pos[4:0] is the lower address for the 32 characters for the ascii text.
assign disp_pos[8:5]  = disp_y[7:4] ;  // the disp_pos[8:5] is the upper address for the 16 lines of text

//  The result from the ascii memory component 'altsyncram_component_osd_mem'  is called letter[7:0]
//  Since disp_pos[8:0] has entered the read address, it takes 2 pixel clock cycles for the resulting letter[7:0] to come out.

//  Now, font_pos[12:0] is the read address for the memory block containing the character specified in letter[]

assign font_pos[9:3] = letter[6:0] ;       // Select the upper font address with the 7 bit letter, note the atari font has only 128 characters.
assign font_pos[2:0] = dly2_disp_y[3:1] ;  // select the font y coordinate with a 2 pixel clock DELAYED disp_y address.  [3:1] is used so that every 2 y lines are repeats

// get the pixel from the x position within the character


//  The resulting 1-bit font image at x is assigned to the OSD[0] output
//  Also, since there is an 8th bit in the ascii text memory, I use that as a second OSD[1] output color bit
assign osd_image[0] = char_line[dly2_disp_x[0]];
assign osd_image[1] = dly2_letter[7];  // Remember, it takes 2 pixel clocks for osd_img[1:0] data to be valid from read address letter[6:0]
                                         // so, if we want to use letter[7] as an upper color bit, is needs to be delayed 2 pixel clocks so it will be parallel with the osd_img[1:0] read data

always @ ( posedge clk ) begin

if (pc_ena) begin

// **************************************************************************************************************************
// *** Create a serial pipe where the PIPE_DELAY parameter selects the pixel count delay for the xxx_in to the xxx_out ports
// **************************************************************************************************************************

hde_pipe[0]   <= hde_in;
hde_pipe[7:1] <= hde_pipe[6:0];
hde_out       <= hde_pipe[PIPE_DELAY-2];

vde_pipe[0]   <= vde_in;
vde_pipe[7:1] <= vde_pipe[6:0];
vde_out       <= vde_pipe[PIPE_DELAY-2];

hs_pipe[0]    <= hs_in;
hs_pipe[7:1]  <= hs_pipe[6:0];
hs_out        <= hs_pipe[PIPE_DELAY-2];

vs_pipe[0]    <= vs_in;
vs_pipe[7:1]  <= vs_pipe[6:0];
vs_out        <= vs_pipe[PIPE_DELAY-2];

// **********************************************************************************************
// This OSD generator's window is only 512 pixels by 256 lines.
// Since the disp_X&Y counters are the screens X&Y coordinates, I'm using an extra most
// significant bit in the counters to determine if the OSD ena flag should be on or off.

if (disp_x[9] || disp_y[8])
dena <= 0; // When disp_x > 511 or disp_y > 255, then turn off the OSD's output enable flag
else
dena <= 1; // otherwise, turn on the OSD output enable flag

if (~vde_in)
disp_y[8:0] <= 9'b111111111; // preset the disp_y counter to max while the vertical display is disabled

else if (hde_in && ~hde_pipe[0])
begin // isolate a single event at the begining of the active display area

disp_x[9:0] <= 10'b0000000000; // clear the disp_x counter
if (!disp_y[8] | (disp_y[8:7] == 2'b11))
disp_y <= disp_y + 1; // only increment the disp_y counter if it hasn't reached it's end

end
else if (!disp_x[9])
disp_x <= disp_x + 1;  // keep on addind to the disp_x counter until it reaches it's end.

// **********************************************************************************************
// *** These delay pipes registers are explained in the 'assign's above
// **********************************************************************************************
dly1_disp_x <= disp_x;
dly2_disp_x <= dly1_disp_x;

dly1_disp_y <= disp_y;
dly2_disp_y <= dly1_disp_y;

dly1_letter <= letter;
dly2_letter <= dly1_letter;

dly1_dena   <= dena;
dly2_dena   <= dly1_dena;
dly3_dena   <= dly2_dena;
dly4_dena   <= dly3_dena;

// **********************************************************************************************
osd_ena_out  <= dly2_dena; // This is used to drive a graphics A/B switch which tells when the OSD graphics should be shown
// It needs to be delayed by the number of pixel clocks required for the above memories

end // ena

end // always@clk

endmodule


But it wont compile - I'm stuck on this error:

Code: [Select]
Error (12152): Can't elaborate user hierarchy "vid_osd_generator:inst2|altsyncram:altsyncram_component_osd_font"

EDIT:

Oh hang on.  I've found a few more errors that may clear this up... watch this space.

EDIT 2:

I've changed altsyncram_component_osd_font as follows:

Code: [Select]
altsyncram altsyncram_component_osd_font ( .wren_a ( wren_font ), .clock0 (clk), .clock1 (clk), .clocken1 (pc_ena),
.address_a (wr_addr[9:0]), .address_b (font_pos[9:0]),
.data_a (wr_data[7:0]), .q_b (sub_wire1));


Still getting errors:

Code: [Select]
Error (272006): In altsyncram megafunction, when OPERATION_MODE parameter is set to DUAL_PORT, total number of bits of port A and port B must be the same
Error (287078): Assertion error: The current megafunction is configured for use with the clear box feature and cannot be used when the clear box feature is disabled
Error (12152): Can't elaborate user hierarchy "vid_osd_generator:inst2|altsyncram:altsyncram_component_osd_font"
Error: Quartus II 64-Bit Analysis & Synthesis was unsuccessful. 3 errors, 15 warnings
Error: Peak virtual memory: 4601 megabytes
Error: Processing ended: Fri Nov 08 17:20:59 2019
Error: Elapsed time: 00:00:02
Error: Total CPU time (on all processors): 00:00:02
Error (293001): Quartus II Full Compilation was unsuccessful. 5 errors, 15 warnings


EDIT 3:

Fixed the first error:
Code: [Select]
wire [7:0] char_line = sub_wire1[7:0];
// w access = 'b111aaaaa aaaaaaaa
altsyncram altsyncram_component_osd_font ( .wren_a ( wren_font ), .clock0 (clk), .clock1 (clk), .clocken1 (pc_ena),
.address_a (wr_addr[9:0]), .address_b (font_pos[9:0]),
.data_a (wr_data[7:0]), .q_b (sub_wire1);


All fixed now.  Time to see if it works... ;)
« Last Edit: November 08, 2019, 05:32:09 pm by nockieboy »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #240 on: November 08, 2019, 05:41:19 pm »
Yeah, that's not working as intended....

EDIT: Just had a hell of a time trying to attach that image - put it at the bottom in the end instead of inline, as the 'edit message' form was just not working!

Here's the current code:

Code: [Select]
module vid_osd_generator ( clk, pc_ena, hde_in, vde_in, hs_in, vs_in, osd_ena_out, osd_image, hde_out, vde_out, hs_out, vs_out,
wren_disp, wren_font, wr_addr, wr_data );

// To write contents into the display and font memories, the wr_addr[15:0] selects the address
// the wr_data[7:0] contains a byte which will be written
// the wren_disp is the write enable for the ascii text ram.  Only the wr_addr[8:0] are used as the character display is 32x16.
// the wren_font is the write enable for the font memory.  Only 2 bits are used of the wr_data[1:0] and wr_addr[12:0] are used.
// tie these ports to GND for now disabling them

input  clk, pc_ena, hde_in, vde_in, hs_in, vs_in;

output osd_ena_out;
reg    osd_ena_out;
output [2:0] osd_image;
output hde_out, vde_out, hs_out, vs_out;
reg hde_out, vde_out, hs_out, vs_out;

input wren_disp, wren_font;
input [15:0] wr_addr;
input [7:0] wr_data;

reg   [9:0] disp_x,dly1_disp_x,dly2_disp_x;
reg   [8:0] disp_y,dly1_disp_y,dly2_disp_y;

reg   dena,dly1_dena,dly2_dena,dly3_dena,dly4_dena;
reg   [7:0] dly1_letter, dly2_letter;

reg   [7:0] hde_pipe, vde_pipe, hs_pipe, vs_pipe;

wire [9:0] font_pos;
wire [8:0]  disp_pos;
wire [2:0]  osd_image;

parameter   PIPE_DELAY =  4;   // This parameter selects the number of pixel clocks to delay the VDE and sync outputs.  Only use 2 through 9.

// ****************************************************************************************************************************
// SCREEN TEXT MEMORY
// ****************************************************************************************************************************
wire [7:0] sub_wire0;
wire [7:0] letter = sub_wire0[7:0];
// w access = 'b1100000a aaaaaaaa
altsyncram altsyncram_component_osd_mem ( .wren_a ( wren_disp ), .clock0 (clk), .clock1 (clk), .clocken1 (pc_ena),
.address_a (wr_addr[8:0]), .address_b (disp_pos[8:0]),
.data_a (wr_data[7:0]), .q_b (sub_wire0));

defparam
altsyncram_component_osd_mem.intended_device_family = "Cyclone II",
altsyncram_component_osd_mem.operation_mode = "DUAL_PORT",
altsyncram_component_osd_mem.width_a = 8,
altsyncram_component_osd_mem.widthad_a = 9,
altsyncram_component_osd_mem.width_b = 8,
altsyncram_component_osd_mem.widthad_b = 9,
altsyncram_component_osd_mem.lpm_type = "altsyncram",
altsyncram_component_osd_mem.width_byteena_a = 1,
altsyncram_component_osd_mem.outdata_reg_b = "CLOCK1",
altsyncram_component_osd_mem.indata_aclr_a = "NONE",
altsyncram_component_osd_mem.wrcontrol_aclr_a = "NONE",
altsyncram_component_osd_mem.address_aclr_a = "NONE",
altsyncram_component_osd_mem.address_reg_b = "CLOCK1",
altsyncram_component_osd_mem.address_aclr_b = "NONE",
altsyncram_component_osd_mem.outdata_aclr_b = "NONE",
altsyncram_component_osd_mem.ram_block_type = "AUTO",
altsyncram_component_osd_mem.init_file = "osd_mem.mif";

// ****************************************************************************************************************************
// FONT MEMORY
// ****************************************************************************************************************************
wire [7:0] sub_wire1;
wire [7:0] char_line = sub_wire1[7:0];
// w access = 'b111aaaaa aaaaaaaa
altsyncram altsyncram_component_osd_font ( .wren_a ( wren_font ), .clock0 (clk), .clock1 (clk), .clocken1 (pc_ena),
.address_a (wr_addr[9:0]), .address_b (font_pos[9:0]),
.data_a (wr_data[7:0]), .q_b (sub_wire1));
defparam
altsyncram_component_osd_font.intended_device_family = "Cyclone II",
altsyncram_component_osd_font.operation_mode = "DUAL_PORT",
altsyncram_component_osd_font.width_a = 8,
altsyncram_component_osd_font.widthad_a = 10,
altsyncram_component_osd_font.width_b = 8,
altsyncram_component_osd_font.widthad_b = 10,
altsyncram_component_osd_font.lpm_type = "altsyncram",
altsyncram_component_osd_font.width_byteena_a = 1,
altsyncram_component_osd_font.outdata_reg_b = "CLOCK1",
altsyncram_component_osd_font.indata_aclr_a = "NONE",
altsyncram_component_osd_font.wrcontrol_aclr_a = "NONE",
altsyncram_component_osd_font.address_aclr_a = "NONE",
altsyncram_component_osd_font.address_reg_b = "CLOCK1",
altsyncram_component_osd_font.address_aclr_b = "NONE",
altsyncram_component_osd_font.outdata_aclr_b = "NONE",
altsyncram_component_osd_font.ram_block_type = "AUTO",
altsyncram_component_osd_font.init_file = "osd_font_8x8bit_128char.mif";

// ****************************************************************************************************************************
// ****************************************************************************************************************************


//  The disp_x is the X coordinate counter.  It runs from 0 to 512 and stops there
//  The disp_y is the Y coordinate counter.  It runs from 0 to 256 and stops there

// Get the character at the current x, y position
assign disp_pos[4:0]  = disp_x[8:4] ;  // The disp_pos[4:0] is the lower address for the 32 characters for the ascii text.
assign disp_pos[8:5]  = disp_y[7:4] ;  // the disp_pos[8:5] is the upper address for the 16 lines of text

//  The result from the ascii memory component 'altsyncram_component_osd_mem'  is called letter[7:0]
//  Since disp_pos[8:0] has entered the read address, it takes 2 pixel clock cycles for the resulting letter[7:0] to come out.

//  Now, font_pos[12:0] is the read address for the memory block containing the character specified in letter[]

assign font_pos[9:3] = letter[6:0] ;       // Select the upper font address with the 7 bit letter, note the atari font has only 128 characters.
assign font_pos[2:0] = dly2_disp_y[3:1] ;  // select the font y coordinate with a 2 pixel clock DELAYED disp_y address.  [3:1] is used so that every 2 y lines are repeats

// get the pixel from the x position within the character


//  The resulting 1-bit font image at x is assigned to the OSD[0] output
//  Also, since there is an 8th bit in the ascii text memory, I use that as a second OSD[1] output color bit
assign osd_image[0] = char_line[dly2_disp_x[0]];
assign osd_image[2] = dly2_letter[7];  // Remember, it takes 2 pixel clocks for osd_img[1:0] data to be valid from read address letter[6:0]
                                       // so, if we want to use letter[7] as an upper color bit, is needs to be delayed 2 pixel clocks so it will be parallel with the osd_img[1:0] read data

always @ ( posedge clk ) begin

if (pc_ena) begin

// **************************************************************************************************************************
// *** Create a serial pipe where the PIPE_DELAY parameter selects the pixel count delay for the xxx_in to the xxx_out ports
// **************************************************************************************************************************

hde_pipe[0]   <= hde_in;
hde_pipe[7:1] <= hde_pipe[6:0];
hde_out       <= hde_pipe[PIPE_DELAY-2];

vde_pipe[0]   <= vde_in;
vde_pipe[7:1] <= vde_pipe[6:0];
vde_out       <= vde_pipe[PIPE_DELAY-2];

hs_pipe[0]    <= hs_in;
hs_pipe[7:1]  <= hs_pipe[6:0];
hs_out        <= hs_pipe[PIPE_DELAY-2];

vs_pipe[0]    <= vs_in;
vs_pipe[7:1]  <= vs_pipe[6:0];
vs_out        <= vs_pipe[PIPE_DELAY-2];

// **********************************************************************************************
// This OSD generator's window is only 512 pixels by 256 lines.
// Since the disp_X&Y counters are the screens X&Y coordinates, I'm using an extra most
// significant bit in the counters to determine if the OSD ena flag should be on or off.

if (disp_x[9] || disp_y[8])
dena <= 0; // When disp_x > 511 or disp_y > 255, then turn off the OSD's output enable flag
else
dena <= 1; // otherwise, turn on the OSD output enable flag

if (~vde_in)
disp_y[8:0] <= 9'b111111111; // preset the disp_y counter to max while the vertical display is disabled

else if (hde_in && ~hde_pipe[0])
begin // isolate a single event at the begining of the active display area

disp_x[9:0] <= 10'b0000000000; // clear the disp_x counter
if (!disp_y[8] | (disp_y[8:7] == 2'b11))
disp_y <= disp_y + 1; // only increment the disp_y counter if it hasn't reached it's end

end
else if (!disp_x[9])
disp_x <= disp_x + 1;  // keep on addind to the disp_x counter until it reaches it's end.

// **********************************************************************************************
// *** These delay pipes registers are explained in the 'assign's above
// **********************************************************************************************
dly1_disp_x <= disp_x;
dly2_disp_x <= dly1_disp_x;

dly1_disp_y <= disp_y;
dly2_disp_y <= dly1_disp_y;

dly1_letter <= letter;
dly2_letter <= dly1_letter;

dly1_dena   <= dena;
dly2_dena   <= dly1_dena;
dly3_dena   <= dly2_dena;
dly4_dena   <= dly3_dena;

// **********************************************************************************************
osd_ena_out  <= dly2_dena; // This is used to drive a graphics A/B switch which tells when the OSD graphics should be shown
// It needs to be delayed by the number of pixel clocks required for the above memories

end // ena

end // always@clk

endmodule

« Last Edit: November 08, 2019, 05:45:12 pm by nockieboy »
 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 5031
  • Country: si
Re: FPGA VGA Controller for 8-bit computer
« Reply #241 on: November 08, 2019, 06:52:41 pm »
Nice work there
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #242 on: November 08, 2019, 07:19:58 pm »
Okay, I realised I was only passing a single bit to char_line to get the x position within the character, so now it's passing the 8-bit x position value.  But the characters were reversed in the x-axis on the screen, so I made the following change:

Code: [Select]
assign osd_image[0] = char_line[(8 - dly2_disp_x[2:0])];
... and now I'm getting this:

868748-0

It's printing every character twice... with some errors at the right hand edge (the last column of the last character on the screen is duplicated once).

EDIT:

Fixed the right-hand edge duplication of the last character column by adding in another delay for x:

Code: [Select]
dly3_disp_x <= dly2_disp_x;
Just got to sort the duplication of each character now...
« Last Edit: November 08, 2019, 07:35:15 pm by nockieboy »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #243 on: November 08, 2019, 07:45:10 pm »
All sorted.  Just needed me to use bits 3:1 in x rather than 2:0 to prevent the duplication of characters - remembered you'd written something about that in the notes somewhere.  Removed the additional delay as it's no longer needed.  :-+

868760-0
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #244 on: November 09, 2019, 12:33:13 am »
Good show  :-+ .  You see all the comments I've added to Verilog code are a must read.

Now:
1) please post the final OSD code so I can proof the working version.
2) Will you be working on the code this weekend? What time?
3) What is you current FMAX?  Which FPGA part# are you using?

Next steps:
1.  Set up the PLL to give you a 125Mhz clock.  This means we will change the PC_ENA coming out of your 'sync_generator' into a 4 bit number with a parameter in the sync generator which selects the maximum count of 4, then resets to 0.  All the attached graphics modules which had 1 wire input PC_ENA will now need PC_ENA[3:0], and all the if (PC_ENA) will change to if (PC_ENA[3:0] == 0).

What we have done here is make you system clock 5x speed, yet divide your pixel clock down to 1/5.  We chose 5 as a magic number and included feeding the PC_ENA full 4 bit phase throughout your design for 2 reasons:
     a) since when you eventually incorporate DVI inside the FPGA, the serial shift out clock is 10x the pixel clock and having the FPGA system feed a DDR IO with 125MHz, you get your 250megabit per second serializer clock with any FPGA, no fancy dedicated hardware serializers.
     b) We will be dedicating memory access time slots during those 5 points, so it is useful to have those 4 bits to allot synchronous events.  Also, the 16$ 2megabit ram lattice part is fast enough to run the entire core at 10x 250MHz, so having the 4 bit PC_ENA, you may just then change that clock divide parameter from 4 to 9 giving you 10 access slots per pixel output.

We should plan what time we are doing this if it is on the weekend...
Next step #2:  Copy and rename the OSD generator to graphics generator.  You will remove both dual port 'altsynccrams' and replace it with a new verilog module 'multiport_gpu_ram.v'.  In your new 'multiport_gpu_ram.v' you will place a single 'altsynccrams' which is 4Kilobytes, and you will make IO registers for:
5 read addressed [20 bit] (Your graphics system will handle a maximum 1 megabyte frame buffer), 5 auxiliary read commands[8bit], 5 data output[8bit], 5 passed through read addresses and 5 passed through auxiliary read commands, all synchronous to the PC_ENA[3:0] position 0, an active pixel cycle.  (WE WILL BEGIN by only specifying the IOs and their functions in the .v module and make sure you understand what everything if for as from this point on, you will be coding everything yourself...)

And IO wires for the second port on the synccram READ/WRITE port for 1 R/W address[20bit] 1 write enable[1 wire], data in[8bit], data out[8bit].  This port is for the Z80, and in future, advanced GPU accelerated processing function (blitter).

Inside the 'multiport_gpu_ram.v', you will have another module 'gpu_dual_port_ram_INTEL.v', where inside that one I'll show you how to configure Quartus' LPM_dualport ram/altsynccram.  This one verilog module, 'gpu_dual_port_ram_INTEL.v'  will change between XILINX & LATTICE as their dual-port memory function blocks have their own setup and parameters written out differently, but function identically.  Other than PLL settings, everything else we have in your GPU project will cross compile in all FPGA vendor 3 ecosystems as our verilog code is 'basement' level.  If the other compilers cant cope with this project, it is their fault...
« Last Edit: November 09, 2019, 12:54:37 am by BrianHG »
 

Offline jhpadjustable

  • Frequent Contributor
  • **
  • Posts: 295
  • Country: us
  • Salt 'n' pepper beard
Re: FPGA VGA Controller for 8-bit computer
« Reply #245 on: November 09, 2019, 03:57:59 am »
Okay, I realised I was only passing a single bit to char_line to get the x position within the character, so now it's passing the 8-bit x position value.  But the characters were reversed in the x-axis on the screen, so I made the following change:

Code: [Select]
assign osd_image[0] = char_line[(8 - dly2_disp_x[2:0])];
Do you see the off-by-one error in this code? You'd do well to learn the bitwise operators and start thinking in binary. Especially the unary ~ operator, same as in C.
« Last Edit: November 09, 2019, 04:01:03 am by jhpadjustable »
"There are more things in heaven and earth, Arduino, than are dreamt of in your philosophy."
 
The following users thanked this post: BrianHG

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #246 on: November 09, 2019, 04:47:49 am »
Okay, I realised I was only passing a single bit to char_line to get the x position within the character, so now it's passing the 8-bit x position value.  But the characters were reversed in the x-axis on the screen, so I made the following change:

Code: [Select]
assign osd_image[0] = char_line[(8 - dly2_disp_x[2:0])];
Do you see the off-by-one error in this code? You'd do well to learn the bitwise operators and start thinking in binary. Especially the unary ~ operator, same as in C.
He had 2 errors, 1 was that my routine was setup for every 2 pixels to be the same, so, disp_x[2:0] should have been disp_x[3:1].  Next, the '8-' should have been '7-'.  As for the 'dly2_disp_x[]', it should actually be 'dly4_disp_x[]' as that bit selection is immediately at the final output, yet reading the font memory takes 2 clocks since it's address was presented.  I still need to proof 'nockieboy's' final code to make sure he knows whats going on and the final results aren't a fluke, or, he figured out about the 2 clock read cycle delay.

The next step after switching to 125MHz will be re-doing the ram and making a simultaneous 5 random port read at every new pixel with 1 huge chunk of 8 bit memory.  Then, a replacement of the X&Y counters for a software programmable address generator with vertical and  horizontal increment sizes and individual X&Y size scales of 1, 2, 3, 4 pixels/lines.  Then a 8 bit color map for the text character, 16 foreground and 16 background colors for each character, going through a 16 bit 16 color palette, 4 bit ARGB.  That's 12 bits 4096 RGB colors + 16 transparency levels since the text mode will sit on top of the graphics mode. (Will look like a TV studio text genlock on top of a video source) The text mode will eat 3 of the 5 access cycles.  The next one will be the 256 color graphic pixel / pixels data (sits below the varible transparency text layer) and the final memory one will be up to 16 sprites per line of video.  (These sit on top of the text layer, or below the text layer, but above the graphics layer.)
« Last Edit: November 09, 2019, 04:50:26 am by BrianHG »
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #247 on: November 09, 2019, 05:52:46 am »
Okay, I realised I was only passing a single bit to char_line to get the x position within the character, so now it's passing the 8-bit x position value.  But the characters were reversed in the x-axis on the screen, so I made the following change:

Code: [Select]
assign osd_image[0] = char_line[(8 - dly2_disp_x[2:0])];
Do you see the off-by-one error in this code? You'd do well to learn the bitwise operators and start thinking in binary. Especially the unary ~ operator, same as in C.
Yes, I agree, the unary ~ operator instead of the '8-' or '7-' would give the compiler less headaches.
 

Offline jhpadjustable

  • Frequent Contributor
  • **
  • Posts: 295
  • Country: us
  • Salt 'n' pepper beard
Re: FPGA VGA Controller for 8-bit computer
« Reply #248 on: November 09, 2019, 06:06:05 am »
Yes, I agree, the unary ~ operator instead of the '8-' or '7-' would give the compiler less headaches.
I dunno, ~ just seemed more natural to me. "Be the bits you wish to see in the world."

Anyway this is turning out to be an interesting lab/demonstration in pair programming. I'm going to try to sit back as a good audience member and watch quietly. :)
"There are more things in heaven and earth, Arduino, than are dreamt of in your philosophy."
 

Online BrianHG

  • Super Contributor
  • ***
  • Posts: 8143
  • Country: ca
    • LinkedIn
Re: FPGA VGA Controller for 8-bit computer
« Reply #249 on: November 09, 2019, 08:07:08 am »
Just personal notes:

The op is currently using a "Cyclone II EP2C5T144".
He has 4608 logic elements / IE 4608 register bits, or, 576 x 8 bit registers.
He has 119808 bits of ram.  Safely, he can make an 8 kilobyte ram plus another 2 kilobyte ram.

Current plan:
Configure main system memory 8 kilobytes.  Contains text, fonts, graphic data & some spare room for CPU in lo-res mode.

        - 1200bytes for 40x30 text mode, or, 4800 bytes for 80x60, or 2400 bytes for 80x30
        - Double the above bytes for text with 16 color foreground and 16 color background.
        - 1024 bytes for 128 character 8x8 font.  2048 bytes for 256 character font.  Double for 8x16 VGA quality font.

        - Additional 16 words by 16 bits for text mode palette.  (This one is small enough to fit in registers if needed.)
        - Additional 256 words by 16 bits for graphics palette.

I hope his next FPGA board is going to have at least 20 kilobytes of ram.  He will be able to make a full VGA grade 16 color text mode, or, 160x120 in 16 color mode.  160x120 in 256 color mode with 32 kilobytes.

The 16$ lattice FPGA can safely be configured to have 192 kilobytes (216kb with a few tricks) of ram (note: 320x240x256color = 75 kilobytes, 640x480x16color = 150kb, 320x240x65k truecolor = 150kb (LOL, an 8 bit computer with a full truecolor desktop background + text based window superimposed)) , plus some spare for palette and a fancy blitter with a command cache.
« Last Edit: November 09, 2019, 10:21:16 am by BrianHG »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf