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

0 Members and 4 Guests are viewing this topic.

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #450 on: November 23, 2019, 06:19:26 pm »
The only pitfall to mention is that the new added output reg, gpu_re_req stays high until the incoming confirmation signal gpu_rd_rdy.  Externally, when muxing, or what will be 'priority' selecting access to the gpu's host memory port will need to understand that when the req goes high, it only needs a single read, and it should only make the rdy pulse high after the right address made it through the ram and the ram's output contents are correct, not before.

Okay, so that'll just need some similar if...end conditionals like the host_handler module has, that's simple enough.

In the case of having the host ram just tied to this one module alone, you would tie the rdy to 1 (vcc) and ignore the req signal as the ram would always feed the data out correctly within 2 clock cycles of the read address change.

Well, this is probably how I'll test it initially - I'll disconnect the RS232 interface and just have the host_interface module connected, then add the RS232 back in later when I'm confident the bus muxer is working for the host_interface and RS232 modules.

If the RAM is feeding back data in 2 clock cycles, don't I need to worry about what's on the data bus in the first two cycles then?
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #451 on: November 23, 2019, 07:07:29 pm »
This is what the simulator is for if you wish to visualize what's going on, however, look at this image:


Running you core at 48Mhz would probably do it all the time, never mind your current 125Mhz core.
« Last Edit: November 23, 2019, 07:09:24 pm by BrianHG »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #452 on: November 23, 2019, 07:47:24 pm »
Remember, all you need is an OE to the FPGA side, or in this case, you just need the 2 control signals setup properly, 'DIR' and 'OE'.  So, I recommend changing your Z80 module's code to just drive those 2 wires.

If I add a 'data_oe', that can drive the OE pin of the 245 along with data_dir.  Hmm.. going to have to think about how to implement that as it takes a non-trivial amount of time for the 245 to switch direction and enable/disable its outputs, i.e. 4-8ns to enable, 4-7ns to disable outputs under typical conditions. 

You will also want to configure the data buss IOs on your FPGA to weak-pullup.  This is for when the buss is in high impedance, your IO's wont float in noise.

Fair point, well made.  :-+

BTY, did you like my additions to you gpu core?

 ??? :-\ Erm... rushes off to check the code.  :o  What changes again?  ^-^

This is what the simulator is for if you wish to visualize what's going on, however, look at this image:
(Attachment Link)

Running you core at 48Mhz would probably do it all the time, never mind your current 125Mhz core.

Yeah, it's pretty clear to me that timings aren't really an issue when I'm running the FPGA at 125 MHz.  ;)  Looks to me like I have nearly 125ns from MREQ going HIGH (and the FPGA shutting off the data bus output) before the next use of the Z80's data bus.

I just need to work out what I'm doing with the OE and DIR pins of the 245.  data_dir (and the h_data_dir pin) control the direction of the 245.  I need a new data_oe output and pin for the 245 OE and some gubbins in the host_handler for them...
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #453 on: November 23, 2019, 07:48:25 pm »
So I've added a 245_OE pin, from data_oe internally to the host_handler.  It runs an inverted signal (see the design image above), so the Verilog remains active positive whilst the output is active LOW, which matches the chip's logic.  data_oe is set LOW from the outset (see code attached)... but I don't think (as usual) it's 100% right.

Code: [Select]
module host_handler (

// input
input wire reset, // GPU reset signal
input wire GPU_CLK, // GPU clock (125 MHz)
//input wire host_CLK, // Microcom clock signal (8 MHz)
input wire host_M1, // Z80 M1 - active LOW
input wire host_MREQ, // Z80 MREQ - active LOW
input wire host_WR, // Z80 WR - active LOW
input wire host_RD, // Z80 RD - active LOW
input wire [21:0]   host_addr, // Microcom 22-bit address bus
input wire [7:0] host_wr_data, // Z80 DATA bus to pass incoming data to GPU RAM
input wire [7:0] gpu_r_data,
input wire gpu_rd_rdy,

// output
//output wire [7:0]  h_rd_data, // Z80 DATA bus to return data from GPU RAM to Z80
//output reg            h_rd_req, //
output reg gpu_wr_ena, // flag HIGH when writing to GPU RAM
output reg data_dir, // control level converter direction for data flow
output reg [19:0]  gpu_addr, // connect to host_addr in vid_osd_generator to address GPU RAM
output reg [7:0]   gpu_wdata, // 8-bit data bus to GPU RAM in vid_osd_generator
output reg [7:0]   host_data,
output reg host_data_ena, // flag HIGH to write data back to Z80
output reg gpu_rd_req,
output reg data_oe // OE for 245 level translator (active LOW)

);

parameter MEMORY_RANGE = 3'b011; // host_addr[21:19] == 3'b011 targets the 512KB 'window' at 0x180000-0x1FFFFF

wire mem_window, Z80_mreq, Z80_write, Z80_read, Write_GPU_RAM, Read_GPU_RAM, GPU_data_oe;

reg last_Z80_WR = 1'b0;  // keep these low on power-up, otherwise a read or write pulse may be triggered
reg last_Z80_RD = 1'b0;

assign mem_window = (host_addr[21:19] == MEMORY_RANGE); // Define an active memory range
assign Z80_mreq = ~host_MREQ && host_M1; // Define a bus memory access state
assign Z80_write = ~host_WR && last_Z80_WR; // Isolate a single write transaction
assign Z80_read = ~host_RD && last_Z80_RD; // Isolate a single read transaction
assign Z80_nread = host_RD && ~last_Z80_RD; // Isolate end of a read transaction

assign Write_GPU_RAM = mem_window && Z80_mreq && Z80_write; // Define a GPU Write action
assign Read_GPU_RAM = mem_window && Z80_mreq && Z80_read; // Define a GPU Read action
assign GPU_data_oe = mem_window && Z80_mreq && ~host_RD; // Define the time the GPU ouputs data onto the Z80 data bus

assign gpu_rd_req = 1'b0;
assign data_oe = 1'b0;

always @ (posedge GPU_CLK)
begin

// is this the start of a WR memory cycle being executed within the GPU's memory window?
if (Write_GPU_RAM) // these compares are finalised on the next GPU_CLK
begin
// yes - pass the address (limited to 512 KB) and data through
data_dir <= GPU_data_oe; // set 245 direction
data_oe <= 1'b1; // enable 245 to allow data through

// ***** I'll need to delay this next bit at least 5ns to allow the 245 to enable its outputs ???

gpu_wdata <= host_wr_data;
gpu_addr <= host_addr[18:0];A
gpu_wr_ena <= 1'b1;

end // memory cycle
else begin

gpu_wr_ena <= 1'b0;

end

if (Read_GPU_RAM)
begin

gpu_addr <= host_addr[18:0]; // pass address to GPU RAM
gpu_rd_req <= 1'b1; // Send a read request to GPU.
data_dir <= GPU_data_oe; // set 245 direction (TO Z80)
data_oe <= 1'b1; // enable 245 data output

end else if (gpu_rd_rdy)
begin

gpu_rd_req <= 1'b0;                // End the read request once the read is ready
host_data[7:0] <= gpu_r_data[7:0]; // Latch the GPU ram read into the output register for the Z80

end else if (gpu_rd_rdy && Z80_nread)
begin

// end of read transaction
data_oe <= 1'b0; // disable 245 data output

end

data_dir      <= GPU_data_oe;
host_data_ena <= GPU_data_oe;

last_Z80_WR <= host_WR;
last_Z80_RD <= host_RD;

end

endmodule

Oh, and apologies for the ridiculous tabs again - it looks fine on my editor, honest!  ::)

I'm thinking of moving the end of 'read request' signal to the // end of read transaction part of the if block.  That way, the mux can turn off gpu_rd_rdy as well.
« Last Edit: November 23, 2019, 07:51:51 pm by nockieboy »
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #454 on: November 23, 2019, 09:50:20 pm »
Ok, I see your point about the switch and output enable.  This is not hard to solve, however, first lets see how and when we want the '245 to be engaged.  Including the FPGA output enable.

Look here:


Legend:
White and green areas, the 245 is disabled.
Green = This is where your 1 shot triggers take place.
Red = Your GPU FPGA output enable's data & the 245 is switched on with direction towards the Z80.
Blue = Switch off all output enables & 245.

Purple,  turn on 245 and make it's direction to feed data to the GPU.
Yellow, at least 8ns later, plus say another 20ns for good measure (this is where defining the input tsu/th values in the compiler becomes important to verify proper timing functionality during fitting, but for now, 20ns is extravagant to a point where you can ignore this.), read the FPGA input port data and write it to memory, and you may also then turn off the 245.  Optionally, you can wait another few ns before turning off the 245.

Remember, during the purple, you do not want the 245 to send any output on the Z80 by accident at all, even for a ns.
Same during the red period, you do not want the 245 to accidentally pulse any output on the FPGA data bus at all.

Let's see what you can code to make the second half (memory write cycle) work properly no matter what.



« Last Edit: November 23, 2019, 10:04:18 pm by BrianHG »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #455 on: November 23, 2019, 11:30:07 pm »
Let's see what you can code to make the second half (memory write cycle) work properly no matter what.

Okay... here's some code:

Code: [Select]
module host_handler (

// input
input wire reset, // GPU reset signal
input wire GPU_CLK, // GPU clock (125 MHz)
//input wire host_CLK, // Microcom clock signal (8 MHz)
input wire host_M1, // Z80 M1 - active LOW
input wire host_MREQ, // Z80 MREQ - active LOW
input wire host_WR, // Z80 WR - active LOW
input wire host_RD, // Z80 RD - active LOW
input wire [21:0]   host_addr, // Microcom 22-bit address bus
input wire [7:0] host_wr_data, // Z80 DATA bus to pass incoming data to GPU RAM
input wire [7:0] gpu_r_data,
input wire gpu_rd_rdy, // one-shot signal from mux that data is ready

// output
//output wire [7:0]  h_rd_data, // Z80 DATA bus to return data from GPU RAM to Z80
//output reg            h_rd_req, //
output reg gpu_wr_ena, // flag HIGH when writing to GPU RAM
output reg data_dir, // control level converter direction for data flow
output reg [19:0]  gpu_addr, // connect to host_addr in vid_osd_generator to address GPU RAM
output reg [7:0]   gpu_wdata, // 8-bit data bus to GPU RAM in vid_osd_generator
output reg [7:0]   host_data,
output reg host_data_ena, // flag HIGH to write data back to Z80
output reg gpu_rd_req,
output reg data_oe // OE for 245 level translator (active LOW)

);

parameter MEMORY_RANGE = 3'b011; // host_addr[21:19] == 3'b011 targets the 512KB 'window' at 0x180000-0x1FFFFF

wire mem_window, Z80_mreq, Z80_write, Z80_read, Write_GPU_RAM, Read_GPU_RAM, GPU_data_oe;

reg last_Z80_WR = 1'b0;  // keep these low on power-up, otherwise a read or write pulse may be triggered
reg last_Z80_RD = 1'b0;
reg data_hold = 1'b0; // used to latch the gpu_rd_rdy signal

assign mem_window = (host_addr[21:19] == MEMORY_RANGE); // Define an active memory range
assign Z80_mreq = ~host_MREQ && host_M1; // Define a bus memory access state
assign Z80_write = ~host_WR && last_Z80_WR; // Isolate a single write transaction
assign Z80_read = ~host_RD && last_Z80_RD; // Isolate a single read transaction
assign Z80_nread = host_RD && ~last_Z80_RD; // Isolate end of a read transaction

assign Write_GPU_RAM = mem_window && Z80_mreq && Z80_write; // Define a GPU Write action
assign Read_GPU_RAM = mem_window && Z80_mreq && Z80_read; // Define a GPU Read action
assign GPU_data_oe = mem_window && Z80_mreq && ~host_RD; // Define the time the GPU ouputs data onto the Z80 data bus

assign gpu_rd_req = 1'b0;
assign data_oe = 1'b0;

always @ (posedge GPU_CLK)
begin

// is this the start of a WR memory cycle being executed within the GPU's memory window?
if (Write_GPU_RAM) // these compares are finalised on the next GPU_CLK
begin
// yes - pass the address (limited to 512 KB) and data through
data_dir <= GPU_data_oe; // set 245 direction
data_oe <= 1'b1; // 'turn on' 245

// ***** I'll need to delay this next bit at least 5ns to allow the 245 to enable its outputs ???

gpu_wdata <= host_wr_data;
gpu_addr <= host_addr[18:0];A
gpu_wr_ena <= 1'b1;

end // memory cycle
else begin

gpu_wr_ena <= 1'b0;

end

if (Read_GPU_RAM)
begin

gpu_addr <= host_addr[18:0]; // pass address to GPU RAM
gpu_rd_req <= 1'b1; // flag a read request to the mux which is one-shotted in the mux
data_dir <= GPU_data_oe; // set 245 direction (TO Z80)
data_oe <= 1'b1; // 'turn on' 245

end else if (gpu_rd_rdy) // gpu_rd_rdy is a one-shot from the mux, reset after one clock
begin

data_hold <= gpu_rd_rdy; // latch the gpu_rd_rdy signal to keep outputting data until Z80 is done with it
host_data_ena <= 1'b1; // set bidir pins to output
gpu_rd_req <= 1'b0;                // End the read request once the read is ready
host_data[7:0] <= gpu_r_data[7:0]; // Latch the GPU ram read into the output register for the Z80

end else if (data_hold && Z80_nread)
begin

// data is being output to Z80, which has signalled end of read transaction
data_oe <= 1'b0; // turn off 245
data_hold <= 1'b0; // reset the latch
host_data_ena <= 1'b0; // set bidir pins to input

end

data_dir      <= GPU_data_oe;
host_data_ena <= GPU_data_oe;

last_Z80_WR <= host_WR;
last_Z80_RD <= host_RD;

end

endmodule


Haven't touched the WRITE conditional, so not sure if it's compatible with the 245 controls in the READ conditional (it's probably not).

Okay, brief explanation of what I was hoping to do with the above code.  If Read_GPU_RAM is HIGH, a read transaction from the Z80 has been detected:
  • First, we grab the address and pass it through (to the mux when it's completed)
  • Then, we assert gpu_rd_req to tell the mux we want to get data from the GPU RAM
  • The 245's direction is switched by setting data_dir to GPU_data_oe
  • Finally, data_oe is asserted HIGH, causing the 245's OE to go LOW and enable its output (the output is inverted in the design)
Then nothing should happen for a couple of cycles, until gpu_rd_rdy is asserted HIGH for one clock cycle by the mux:
  • data_hold is asserted HIGH to latch the data output to the Z80 for a time
  • gpu_rd_req is cleared LOW to stop any further read requests to the mux (which will ignore them as it's a one-shot signal)
  • host_data_ena is asserted to make the data bidir pins outputs
  • the data from the mux is passed-through to the data output for the Z80 to read
This state is held until the Z80 has read the data and ends the read cycle by setting RD HIGH.  This triggers the last part of the conditional structure:
  • data_oe is set LOW, making the 245's data pins tri-state
  • data_hold is set LOW, ready for the next read transaction
  • host_data_ena is set LOW to reset bidir pins to input
I think that might work...  ???  But as I said, not sure about the write conditional now.
« Last Edit: November 23, 2019, 11:38:17 pm by nockieboy »
 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 4957
  • Country: si
Re: FPGA VGA Controller for 8-bit computer
« Reply #456 on: November 24, 2019, 09:00:55 am »
Small tip you probably want to distinguish any active low signals with something in the name. Like for example calling "host_RD" instead "host_RDn" or "host_RD_N" or "host_nRD" or whatever. There is no standard convention for it, my personal preference is adding a extra n on the end (I use this on my cirucit schematics too).

As for the bus coming out of there into memory you could consider using one of the FPGA bus standards so that naming of the signals is firmly defined and the documentation for that bus is already written. The bus specification has well thought out rules for how to use the bus in a way that makes it compatible with a wide range of situations and the documentation PDF will have lots of timing diagrams (I find these very useful when programing HDL as i can see exactly what needs to happen on what clock edge).

The standard "open source" FPGA bus is called Wishbone: https://en.wikipedia.org/wiki/Wishbone_(computer_bus)
Altera has its own proprietary spec called the Avalon bus: https://www.intel.com/content/www/us/en/programmable/documentation/nik1412467993397.html
There are plenty of other buses like the AXI AHB...etc that are used in ARM processors and lots more but they are complex due to all the features they have.

Technically there is no clear performance advantage of one bus over another, for basic functionality they all do the same thing. When you are developing for Intel/Altera there is however some advantage to be had in using the Avalon bus. Since its a bus they have developed means that all IP modules (Such as a SDRAM controller) you get from Altera will have this same bus interface and so will be easy to connect to other Avalon buses. Also if you want to use the Quartus Qsys (formerly SOPC Builder) to auto-magicaly generate the memory bus switching fabric, then you need to have Avalon buses on all your blocks. For simple systems Qsys is not that useful, but when you start having many devices connected by a memory bus and multiple masters and all that it can be an amazing help. You just hook up your devices to the bus with a single line and Qsys will figure out how to make that happen under the hood. It works even if the buses are different such as 32bit bus going into a 8bit one, two buses running on different clocks, different latency... etc as it will insert the appropriate bus adapter to convert it. If there are multiple masters on a bus it will automatically insert arbitration logic to make all the masters play nice with each other without the master even knowing about it (bus just looks busy for a unusually long time if another master is using it). It also takes care of all address decoding for you so you simply edit your address map in a table for where in memory each device is supposed to appear.

Making use of Qsys does require a bit of extra work tho. In addition it giving it the .v file of your module you also have to explain to it what pins do what. It needs to know where every pin of the Avalon bus is, how many buses there are (You can have 10 memory buses into one block if you like) and for each bus it needs to know the timing specifications such as what latency the read and write cycles happen on, if it supports burst transfers etc.. For example i did this when i had a board with a OMAP 138L microprocessor(450MHz ARM+DSP) and a FPGA. The processor had a SRAM style bus going out of it that my FPGA sat on as a peripheral much like you are doing here with the Z80. So i wrote a module that converts a SRAM style bus into Avalon and turned it into a Qsys block. Then i could just throw a DDR2 memory controller, some GPIO for LEDs and other peripherals into the bus and so with only a few clicks they would magically appear accessible to the ARM processor in that SRAM memory window.
« Last Edit: November 24, 2019, 09:04:54 am by Berni »
 
The following users thanked this post: nockieboy

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #457 on: November 24, 2019, 10:27:33 am »
Small tip you probably want to distinguish any active low signals with something in the name. Like for example calling "host_RD" instead "host_RDn" or "host_RD_N" or "host_nRD" or whatever. There is no standard convention for it, my personal preference is adding a extra n on the end (I use this on my cirucit schematics too).

As for the bus coming out of there into memory you could consider using one of the FPGA bus standards so that naming of the signals is firmly defined and the documentation for that bus is already written. The bus specification has well thought out rules for how to use the bus in a way that makes it compatible with a wide range of situations and the documentation PDF will have lots of timing diagrams (I find these very useful when programing HDL as i can see exactly what needs to happen on what clock edge).

The standard "open source" FPGA bus is called Wishbone: https://en.wikipedia.org/wiki/Wishbone_(computer_bus)
Altera has its own proprietary spec called the Avalon bus: https://www.intel.com/content/www/us/en/programmable/documentation/nik1412467993397.html
There are plenty of other buses like the AXI AHB...etc that are used in ARM processors and lots more but they are complex due to all the features they have.


     I recommended using the 'n' for negative signals as well.  The wishbone is actually a nice choice.  The problem with Avalon is that it's Intel proprietary.  In actually, the Z80 is the project's only interface with an outside third party device and the rest is a register memory bank mentioned in point #3 here : Unified HW-Control Register Bank and a programmable horizontal and vertical counter mentioned here: Video RAM pointer address Generator & Pixel Size Features .  Other than 2 small palette rams taking the feed from the core memory output fed by the address generators, this would create a very powerful GPU for an 8 bit Z80.

    This thread is part work, part Verilog learning, part how to sequence, and I trying to get nockieboy and anyone else reading this thread to begin using a simulation tool, if any but a quick one at minimum just to see how important such a tool is, then maybe he will advance to full code style simulators which are well documented like Modelsim.

    In helping nockieboy, I let him begin his idea, suggest some code cleaning techniques & recommendations, then just as thing look to finish, I post a question on how not necessarily make the most useful code, but show how simple it may be to gets as a solution using thing he already learned but in a different way as you will see in my next post.

« Last Edit: November 24, 2019, 10:30:20 am by BrianHG »
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #458 on: November 24, 2019, 10:51:42 am »
Let's see what you can code to make the second half (memory write cycle) work properly no matter what.

Okay... here's some code:

Code: [Select]
module host_handler (

// input
input wire reset, // GPU reset signal
input wire GPU_CLK, // GPU clock (125 MHz)
//input wire host_CLK, // Microcom clock signal (8 MHz)
input wire host_M1, // Z80 M1 - active LOW
input wire host_MREQ, // Z80 MREQ - active LOW
input wire host_WR, // Z80 WR - active LOW
input wire host_RD, // Z80 RD - active LOW
input wire [21:0]   host_addr, // Microcom 22-bit address bus
input wire [7:0] host_wr_data, // Z80 DATA bus to pass incoming data to GPU RAM
input wire [7:0] gpu_r_data,
input wire gpu_rd_rdy, // one-shot signal from mux that data is ready

// output
//output wire [7:0]  h_rd_data, // Z80 DATA bus to return data from GPU RAM to Z80
//output reg            h_rd_req, //
output reg gpu_wr_ena, // flag HIGH when writing to GPU RAM
output reg data_dir, // control level converter direction for data flow
output reg [19:0]  gpu_addr, // connect to host_addr in vid_osd_generator to address GPU RAM
output reg [7:0]   gpu_wdata, // 8-bit data bus to GPU RAM in vid_osd_generator
output reg [7:0]   host_data,
output reg host_data_ena, // flag HIGH to write data back to Z80
output reg gpu_rd_req,
output reg data_oe // OE for 245 level translator (active LOW)

);

parameter MEMORY_RANGE = 3'b011; // host_addr[21:19] == 3'b011 targets the 512KB 'window' at 0x180000-0x1FFFFF

wire mem_window, Z80_mreq, Z80_write, Z80_read, Write_GPU_RAM, Read_GPU_RAM, GPU_data_oe;

reg last_Z80_WR = 1'b0;  // keep these low on power-up, otherwise a read or write pulse may be triggered
reg last_Z80_RD = 1'b0;
reg data_hold = 1'b0; // used to latch the gpu_rd_rdy signal

assign mem_window = (host_addr[21:19] == MEMORY_RANGE); // Define an active memory range
assign Z80_mreq = ~host_MREQ && host_M1; // Define a bus memory access state
assign Z80_write = ~host_WR && last_Z80_WR; // Isolate a single write transaction
assign Z80_read = ~host_RD && last_Z80_RD; // Isolate a single read transaction
assign Z80_nread = host_RD && ~last_Z80_RD; // Isolate end of a read transaction

assign Write_GPU_RAM = mem_window && Z80_mreq && Z80_write; // Define a GPU Write action
assign Read_GPU_RAM = mem_window && Z80_mreq && Z80_read; // Define a GPU Read action
assign GPU_data_oe = mem_window && Z80_mreq && ~host_RD; // Define the time the GPU ouputs data onto the Z80 data bus

assign gpu_rd_req = 1'b0;
assign data_oe = 1'b0;

always @ (posedge GPU_CLK)
begin

// is this the start of a WR memory cycle being executed within the GPU's memory window?
if (Write_GPU_RAM) // these compares are finalised on the next GPU_CLK
begin
// yes - pass the address (limited to 512 KB) and data through
data_dir <= GPU_data_oe; // set 245 direction
data_oe <= 1'b1; // 'turn on' 245

// ***** I'll need to delay this next bit at least 5ns to allow the 245 to enable its outputs ???

gpu_wdata <= host_wr_data;
gpu_addr <= host_addr[18:0];A
gpu_wr_ena <= 1'b1;

end // memory cycle
else begin

gpu_wr_ena <= 1'b0;

end

if (Read_GPU_RAM)
begin

gpu_addr <= host_addr[18:0]; // pass address to GPU RAM
gpu_rd_req <= 1'b1; // flag a read request to the mux which is one-shotted in the mux
data_dir <= GPU_data_oe; // set 245 direction (TO Z80)
data_oe <= 1'b1; // 'turn on' 245

end else if (gpu_rd_rdy) // gpu_rd_rdy is a one-shot from the mux, reset after one clock
begin

data_hold <= gpu_rd_rdy; // latch the gpu_rd_rdy signal to keep outputting data until Z80 is done with it
host_data_ena <= 1'b1; // set bidir pins to output
gpu_rd_req <= 1'b0;                // End the read request once the read is ready
host_data[7:0] <= gpu_r_data[7:0]; // Latch the GPU ram read into the output register for the Z80

end else if (data_hold && Z80_nread)
begin

// data is being output to Z80, which has signalled end of read transaction
data_oe <= 1'b0; // turn off 245
data_hold <= 1'b0; // reset the latch
host_data_ena <= 1'b0; // set bidir pins to input

end

data_dir      <= GPU_data_oe;
host_data_ena <= GPU_data_oe;

last_Z80_WR <= host_WR;
last_Z80_RD <= host_RD;

end

endmodule


Haven't touched the WRITE conditional, so not sure if it's compatible with the 245 controls in the READ conditional (it's probably not).

Okay, brief explanation of what I was hoping to do with the above code.  If Read_GPU_RAM is HIGH, a read transaction from the Z80 has been detected:
  • First, we grab the address and pass it through (to the mux when it's completed)
  • Then, we assert gpu_rd_req to tell the mux we want to get data from the GPU RAM
  • The 245's direction is switched by setting data_dir to GPU_data_oe
  • Finally, data_oe is asserted HIGH, causing the 245's OE to go LOW and enable its output (the output is inverted in the design)
Then nothing should happen for a couple of cycles, until gpu_rd_rdy is asserted HIGH for one clock cycle by the mux:
  • data_hold is asserted HIGH to latch the data output to the Z80 for a time
  • gpu_rd_req is cleared LOW to stop any further read requests to the mux (which will ignore them as it's a one-shot signal)
  • host_data_ena is asserted to make the data bidir pins outputs
  • the data from the mux is passed-through to the data output for the Z80 to read
This state is held until the Z80 has read the data and ends the read cycle by setting RD HIGH.  This triggers the last part of the conditional structure:
  • data_oe is set LOW, making the 245's data pins tri-state
  • data_hold is set LOW, ready for the next read transaction
  • host_data_ena is set LOW to reset bidir pins to input
I think that might work...  ???  But as I said, not sure about the write conditional now.


Ok, now that you see it may need to sequence a set of events after a detection of a write, let's start by changing your:
Code: [Select]
if (Write_GPU_RAM) // these compares are finalised on the next GPU_CLK
begin ....
        ....end


To something like this:
Code: [Select]
reg [9:0] Z80_write_sequencer;
......
always
.......

Z80_write_sequencer[0] <= Write_GPU_RAM ;
Z80_write_sequencer[9:1] <= Z80_write_sequencer[8:0] ;

//Next... the new IF()'s

if ( Z80_write_sequencer[#] )    'perform a function, say, select 245 dir
if ( Z80_write_sequencer[#] )    'perform a function, say, make sure FPGA IO is in input mode.
if ( Z80_write_sequencer[#] )    'perform a function, say, turn on 245 OE
if ( Z80_write_sequencer[#] )    'perform a function, say, latch Z80 Write address
if ( Z80_write_sequencer[#] )    'perform a function, say, latch Z80 data bus
if ( Z80_write_sequencer[#] )    'perform a function, say, turn on FPGA ram we
if ( Z80_write_sequencer[#] )    'perform a function, say, turn off FPGA ram we
if ( Z80_write_sequencer[#] )    'perform a function, say, turn off 245 OE

.........



     Remember, the 'Write_GPU_RAM' only pulses high for 1 clock exactly right at the detection of a valid write state.  That pulse will now shift down the 'Z80_write_sequencer[9:0] ' pipe.  Now, if you choose the right #, you can keep everything clean and functional, even if the Z80 is running at a synchronous 1/2 clock speed to you GPU clock.  To make you code really nice, make a parameter something like 'BIDIR_245_Delay_clock_cycles', or, with a little math, instead use 'GPU_CLK_Frequency' and 'BIDIR_245_Delay_ns' to shift those those sequencer[#'s] so your code may auto generate the positions if you wish to run the project at any other frequency like 135MHz, or 250MHz, or 270MHz.

Bonus hint (but not necessary at your clock speed): There is a way to push this entire sequence chain up by one clock cycle sooner, let's see if you can figure this out...
« Last Edit: November 24, 2019, 01:10:53 pm by BrianHG »
 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 4957
  • Country: si
Re: FPGA VGA Controller for 8-bit computer
« Reply #459 on: November 24, 2019, 11:54:09 am »
]
     I recommended using the 'n' for negative signals as well.  The wishbone is actually a nice choice.  The problem with Avalon is that it's Intel proprietary.  In actually, the Z80 is the project's only interface with an outside third party device and the rest is a register memory bank mentioned in point #3 here : Unified HW-Control Register Bank and a programmable horizontal and vertical counter mentioned here: Video RAM pointer address Generator & Pixel Size Features .  Other than 2 small palette rams taking the feed from the core memory output fed by the address generators, this would create a very powerful GPU for an 8 bit Z80.

    This thread is part work, part Verilog learning, part how to sequence, and I trying to get nockieboy and anyone else reading this thread to begin using a simulation tool, if any but a quick one at minimum just to see how important such a tool is, then maybe he will advance to full code style simulators which are well documented like Modelsim.

    In helping nockieboy, I let him begin his idea, suggest some code cleaning techniques & recommendations, then just as thing look to finish, I post a question on how not necessarily make the most useful code, but show how simple it may be to gets as a solution using thing he already learned but in a different way as you will see in my next post.

Well these standard buses (even if proprietary to a FPGA vendor) don't really lock you in to exclusively using that bus. Nobody stopping you in using a Avalon bus in a Xilinx or Lattice FPGA and no special vendor specific IP is needed to make them work. Its mostly just a common convention to make things easily work together. Much like the convention of water taps having hot left and cold right, no reason why it can't be the other way around, but sticking to a standard saves effort by not having to check where is what, it just works.

A pair of similar Wishbone and Avalon buses can sometimes be adapted to each other by simply connecting the right pins together, no logic gates at all. Sometimes it might need a few NOT and AND gates to flip and merge some signals that work differently but that's it. Its only more complex if the buses are very different (mismatched number of bits, incompatible timings, missing byte selects etc..) but for those even connecting two standard Wishbone buses needs an adapter with lots of logic, but so as long as the buses are similar its all mostly directly connected. Even the Z80 bus looks quite similar to one of the possible Wishbone bus configurations (if you rename the signals a bit and split out the bidirectional data bus). What makes adapting the Z80 bus to the internal bus more complex is mostly due to the Z80 bus running at a different clock than the internal bus. So everything needs to be buffered between them, but still not all that hard. Its mostly about using a bus with a offical name so that you can google the bus name and get documentation on how it should work with diagrams and everything. If i make my own bus i know id do a terrible job at documenting it for the future me 5 years later looking at the code and not understanding it.

On Lattice FPGAs i have indeed been sticking to the Wishbone bus because its the most popular non proprietary bus spec. Sticking to the bus spec also helps with simulation, once you have built testbenches for a wishbone master and slave you find that you can reuse those testbenches with little to no modification, exercising most other wishbone enabled modules you write yourself.
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #460 on: November 24, 2019, 12:44:19 pm »
Ok, yes, I do think that 'nockieboy's following bridge interface naming convention could be amended:
877762-0

We know it is a Z80 interface specifically with Z80 names and IO symbols on one side which can be confused since his uses 'h_xxx', and the '_xxx' may be confused with the his other 'h_xxx' inside the FPGA.  What belongs where?  And how to use?

The 'h_' connections for the GPU ram should have been just been a GPU_RAM... port.  And his Z80 Verilog bridge should have had Z80_xxx names for each Z80_ signal.  And maybe the 245 buffer controls could have been labeled 'Z80_245buffer_xx'.  OMG, this would end the difficult time I have reading his code & understanding what's being wired where and how it works without needing the block diagram illustrations.

Yes, using the Avalon bus is nice for many.   

This project will eventually need read and write simultaneously to the GPU ram with 0 wait states, as this project will have a hardware draw / copy function which shares the Z80's GPU memory port.  And we want to fill/draw/copy as close to the 125Mhz in million pixels per second as possible, minus the 2.7 million possible Z80 transactions a second.  So, for this project, Yes on adopting the Avalon naming scheme if you want, but, we will need to hard wire each specific unidirectional address/data read path, write path channels into a priority encoder/selector, and take the return data into the right registers with acknowledge will be nothing more than a address & read req, or write req on each dedicated address line to the ram.  Coming out will be the read rdy which is just the read_req piped through with the right clk delay of the FPGA memory core.  That's 2 or 3 wires in and 2 or 3 wires out.  (Counting the data and address as 1 wire + the req signal).  There won't be time for any bus arbitration except on this tiny Z80 interface bridge.

     Now, here is the big + for Avalon.  Instead of this Z80 to GPU ram module which is currently being designed, but, if a GPU to Avalon module was being designed instead, and, in the public domain, a Z80 to Avalon bridge exists, as well as 6502 to Avalon, as well as 68000 to Avalon, ect, this may be the perfect spot to use Avalon.  However, once again, on the other side of this Avalon bridge where the GPU ram port resides, we just want those 3 wire interface ports since we want 3 to 5 new high speed parallel channels to access the GPU ram directly as static ram.  The other big + if if someone else want to port this GPU into their project, the Avalon bridge link would help if they are familiar, however, since the 'host' GPU ram interface is only supposed to be and address, data in and data out with a write enable, just using the Avalon correct 4 labels would be enough to function.

     Note that inside the current project, the 5 existing 5 port parallel access to the GPU ram already exists with only 1 address input and 1 data output, and currently unused, a request auxiliary flag input and output as well as passing the address through.
« Last Edit: November 24, 2019, 02:30:27 pm by BrianHG »
 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 4957
  • Country: si
Re: FPGA VGA Controller for 8-bit computer
« Reply #461 on: November 24, 2019, 01:28:20 pm »
Avalon does have the ability to work in a zero wait state configuration that functions a lot like SRAM.

I also tend to write my Wishbone peripherals to be zero wait state for registers since that lets me ignore most of the signals (Its just a MUX controlled by the address bus that puts the correct register out on the bus regardless if its being read or not). If this big pure combinatorial read path ends up being too slow i can always just make it raise its wait line for the first cycle and this turns it into 1 wait state latency.

There are also specs for read-modify-write operations on the bus that could be used to transfer data in both directions simultaneously. As for multiple data on single address can be implemented by concatenating the data buses into one wide data bus. can be 1024 bits wide if needed. If multiple address and multiple data are needed the entire bus could be duplicated as many times as needed.

But yes i agree that RAM will rarely have a zero wait state bus coming out of it. It is possible to do for internal block RAM, but running any sort of external RAM (even just SRAM) will tend to have at least 1 wait state, DRAMs will tend to have even longer wait states. So in order to get full speed out of those pipelined burst transfers must be used and these are supposed by both Wishbone and Avalon. But yeah i would stick with the fast internal block ram for as long as possible because writing pipelined Verilog code that milks every last clock cycle for performance still hurts my brain. Sometimes special requirements do require a special bus, but you can still stick to the naming conventions so that you can know what a signal is for at a glance.

I do agree the usual memory bus arbitration is a poor fit for the critical real time buses inside the GPU where guaranteed bandwidth is required. Using a normal memory bus with multi-master arbitration is better suited on the bus before the GPU where you might have other hardware sitting on it such as the sound card, IO controller, DMA etc.. that all might want to talk to each other in some way and all make use of a single large external memory of a few Megabytes in size.
 
The following users thanked this post: BrianHG

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #462 on: November 24, 2019, 02:29:15 pm »
There are also specs for read-modify-write operations on the bus that could be used to transfer data in both directions simultaneously. As for multiple data on single address can be implemented by concatenating the data buses into one wide data bus. can be 1024 bits wide if needed. If multiple address and multiple data are needed the entire bus could be duplicated as many times as needed.
This really legitimizes Avalon capabilities...
So many getting into 16/32/64 bit designs always ignore whenever we want to write bytes in the middle only, but our memory necessarily doesn't have byte mask features.  Ir in the case of a GPU, maybe we are using 2 pixels per byte in 16 color mode and you want to hardware accelerate drawing on only one of the 2 pixels.  Read-modify-write, as well as any simple logic done to a pixel like a transparency stencil when copying blocks of memory becomes ever so useful.
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #463 on: November 24, 2019, 03:28:30 pm »
Ok, now that you see it may need to sequence a set of events after a detection of a write, let's start by changing your:
Code: [Select]
if (Write_GPU_RAM) // these compares are finalised on the next GPU_CLK
begin ....
        ....end


To something like this:
Code: [Select]
reg [9:0] Z80_write_sequencer;
......
always
.......

Z80_write_sequencer[0] <= Write_GPU_RAM ;
Z80_write_sequencer[9:1] <= Z80_write_sequencer[8:0] ;

//Next... the new IF()'s

if ( Z80_write_sequencer[#] )    'perform a function, say, select 245 dir
if ( Z80_write_sequencer[#] )    'perform a function, say, make sure FPGA IO is in input mode.
if ( Z80_write_sequencer[#] )    'perform a function, say, turn on 245 OE
if ( Z80_write_sequencer[#] )    'perform a function, say, latch Z80 Write address
if ( Z80_write_sequencer[#] )    'perform a function, say, latch Z80 data bus
if ( Z80_write_sequencer[#] )    'perform a function, say, turn on FPGA ram we
if ( Z80_write_sequencer[#] )    'perform a function, say, turn off FPGA ram we
if ( Z80_write_sequencer[#] )    'perform a function, say, turn off 245 OE

.........



     Remember, the 'Write_GPU_RAM' only pulses high for 1 clock exactly right at the detection of a valid write state.  That pulse will now shift down the 'Z80_write_sequencer[9:0] ' pipe.  Now, if you choose the right #, you can keep everything clean and functional, even if the Z80 is running at a synchronous 1/2 clock speed to you GPU clock.  To make you code really nice, make a parameter something like 'BIDIR_245_Delay_clock_cycles', or, with a little math, instead use 'GPU_CLK_Frequency' and 'BIDIR_245_Delay_ns' to shift those those sequencer[#'s] so your code may auto generate the positions if you wish to run the project at any other frequency like 135MHz, or 250MHz, or 270MHz.

Bonus hint (but not necessary at your clock speed): There is a way to push this entire sequence chain up by one clock cycle sooner, let's see if you can figure this out...

Okay, so I really should be getting used to using pipes by now, but I keep coming at these problems from a programming point of view and this isn't programming.  ;)

So at 125 MHz, one clock cycle takes 8ns - I guess that should be enough for the 245 to enable its outputs and/or change direction, so for the moment I'm using position [1] in the Z80_write_sequencer to get the data from the data bus and pass it to the GPU RAM.  Can always up it to 2 if there's data instability.

Here's the code:

Code: [Select]
module host_handler (

// input
input wire reset, // GPU reset signal
input wire GPU_CLK, // GPU clock (125 MHz)
//input wire host_CLK, // Microcom clock signal (8 MHz)
input wire Z80_M1n, // Z80 M1 - active LOW
input wire Z80_MREQn, // Z80 MREQ - active LOW
input wire Z80_WRn, // Z80 WR - active LOW
input wire Z80_RDn, // Z80 RD - active LOW
input wire [21:0] Z80_addr, // Microcom 22-bit address bus
input wire [7:0] Z80_wData, // Z80 DATA bus to pass incoming data to GPU RAM
input wire [7:0] gpu_rData,
input wire gpu_rd_rdy, // one-shot signal from mux that data is ready

// output
//output wire [7:0]  h_rd_data, // Z80 DATA bus to return data from GPU RAM to Z80
//output reg            h_rd_req, //
output reg gpu_wr_ena, // flag HIGH when writing to GPU RAM
output reg data_dir, // control level converter direction for data flow
output reg [19:0]  gpu_addr, // connect to Z80_addr in vid_osd_generator to address GPU RAM
output reg [7:0]   gpu_wdata, // 8-bit data bus to GPU RAM in vid_osd_generator
output reg [7:0]   Z80_rData,
output reg Z80_rData_ena, // flag HIGH to write data back to Z80
output reg gpu_rd_req,
output reg data_oe // OE for 245 level translator (active LOW)

);

parameter MEMORY_RANGE = 3'b011; // Z80_addr[21:19] == 3'b011 targets the 512KB 'window' at 0x180000-0x1FFFFF
parameter DELAY_CYCLES = 1; // number of cycles to delay write for 245

wire mem_window, Z80_mreq, Z80_write, Z80_read, Z80_nRead, Write_GPU_RAM, Read_GPU_RAM, GPU_data_oe;

reg last_Z80_WR = 1'b0;  // keep these low on power-up, otherwise a read or write pulse may be triggered
reg last_Z80_RD = 1'b0;
reg data_hold = 1'b0; // used to latch the gpu_rd_rdy signal
reg [9:0] Z80_write_sequencer;

assign mem_window = (Z80_addr[21:19] == MEMORY_RANGE); // Define an active memory range
assign Z80_mreq = ~Z80_MREQn && Z80_M1n; // Define a bus memory access state
assign Z80_write = ~Z80_WRn && last_Z80_WR; // Isolate a single write transaction
assign Z80_read = ~Z80_RDn && last_Z80_RD; // Isolate a single read transaction
assign Z80_nRead = Z80_RDn && ~last_Z80_RD; // Isolate end of a read transaction

assign Write_GPU_RAM = mem_window && Z80_mreq && Z80_write; // Define a GPU Write action
assign Read_GPU_RAM = mem_window && Z80_mreq && Z80_read; // Define a GPU Read action
assign GPU_data_oe = mem_window && Z80_mreq && ~Z80_RDn; // Define the time the GPU ouputs data onto the Z80 data bus

assign gpu_rd_req = 1'b0; // default gpu_rd_req to LOW
assign data_oe = 1'b0; // disable 245 output
assign Z80_rData_ena = 1'b0; // set Z80 data pins to input on the FPGA

always @ (posedge GPU_CLK)
begin

Z80_write_sequencer[0] <= Write_GPU_RAM;
Z80_write_sequencer[9:1] <= Z80_write_sequencer[8:0];

if (Z80_write_sequencer[0])
begin

// start of Z80 WRite detected
data_dir <= GPU_data_oe; // set 245 direction
Z80_rData_ena <= 1'b0; // set FPGA pins to input (should be by default)
data_oe <= 1'b1; // enable 245 output

end
else if (Z80_write_sequencer[DELAY_CYCLES]) // at 125 MHz, 1 cycle = 8ns - just enough time for the 245 to change direction & enable outputs
begin

// pass address (limited to 512 KB) and data through to mux
gpu_wdata <= Z80_wData;
gpu_addr <= Z80_addr[18:0];
gpu_wr_ena <= 1'b1;

end
else if (Z80_write_sequencer[DELAY_CYCLES + 1])
begin

// end of write to GPU RAM
gpu_wr_ena <= 1'b0;
data_oe <= 1'b0; // disable 245 output

end

if (Read_GPU_RAM)
begin

gpu_addr <= Z80_addr[18:0];// pass address to GPU RAM
gpu_rd_req <= 1'b1; // flag a read request to the mux which is one-shotted in the mux
data_dir <= GPU_data_oe; // set 245 direction (TO Z80)
data_oe <= 1'b1; // 'turn on' 245

end else if (gpu_rd_rdy) // gpu_rd_rdy is a one-shot from the mux, reset after one clock
begin

data_hold <= gpu_rd_rdy; // latch the gpu_rd_rdy signal to keep outputting data until Z80 is done with it
Z80_rData_ena <= 1'b1; // set bidir pins to output
gpu_rd_req <= 1'b0; // End the read request once the read is ready
Z80_rData[7:0] <= gpu_rData[7:0]; // Latch the GPU ram read into the output register for the Z80

end else if (data_hold && Z80_nRead)
begin

// data is being output to Z80, which has signalled end of read transaction
data_oe <= 1'b0; // disable 245 output
data_hold <= 1'b0; // reset the latch
Z80_rData_ena <= 1'b0; // set bidir pins to input

end

data_dir      <= GPU_data_oe;
Z80_rData_ena <= GPU_data_oe;

last_Z80_WR <= Z80_WRn;
last_Z80_RD <= Z80_RDn;

end

endmodule


I've had a go making the naming convention a little more consistent, but as always I'm open to advice, recommendations and (constructive!) criticism!  ;)
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #464 on: November 24, 2019, 04:29:28 pm »

I've had a go making the naming convention a little more consistent, but as always I'm open to advice, recommendations and (constructive!) criticism!  ;)
Yes, you got the idea, however, please change the naming convention to what I said 2 posts ago.  All of it.

2: I know you love squishing everything into 1 'IF' within it's 'begin' and 'end', for this case, write it out my way without the begin & end like so:
Code: [Select]
if ( Z80_write_sequencer[#] )    'perform a function, say, select 245 dir
if ( Z80_write_sequencer[#] )    'perform a function, say, make sure FPGA IO is in input mode.
if ( Z80_write_sequencer[#] )    'perform a function, say, turn on 245 OE
if ( Z80_write_sequencer[#] )    'perform a function, say, latch Z80 Write address
if ( Z80_write_sequencer[#] )    'perform a function, say, latch Z80 data bus
if ( Z80_write_sequencer[#] )    'perform a function, say, turn on FPGA ram we
if ( Z80_write_sequencer[#] )    'perform a function, say, turn off FPGA ram we
if ( Z80_write_sequencer[#] )    'perform a function, say, turn off 245 OE
And I'm about to show you why once you pasted the changes back here....
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #465 on: November 24, 2019, 05:24:19 pm »
Yes, you got the idea, however, please change the naming convention to what I said 2 posts ago.  All of it.

Err okay, I thought I had?  I've just changed the 245 signal names, but other than that, I'm not sure what else you want changed?  :-//

The inputs from the Z80 all start with 'Z80_', all have an 'n' at the end where they're active low...  :scared:

2: I know you love squishing everything into 1 'IF' within it's 'begin' and 'end', for this case, write it out my way without the begin & end like so:
Code: [Select]
if ( Z80_write_sequencer[#] )    'perform a function, say, select 245 dir
if ( Z80_write_sequencer[#] )    'perform a function, say, make sure FPGA IO is in input mode.
if ( Z80_write_sequencer[#] )    'perform a function, say, turn on 245 OE
if ( Z80_write_sequencer[#] )    'perform a function, say, latch Z80 Write address
if ( Z80_write_sequencer[#] )    'perform a function, say, latch Z80 data bus
if ( Z80_write_sequencer[#] )    'perform a function, say, turn on FPGA ram we
if ( Z80_write_sequencer[#] )    'perform a function, say, turn off FPGA ram we
if ( Z80_write_sequencer[#] )    'perform a function, say, turn off 245 OE

And I'm about to show you why once you pasted the changes back here....

Okay, have done the above - updated code below.

Code: [Select]
module host_handler (

// input
input wire reset, // GPU reset signal
input wire GPU_CLK, // GPU clock (125 MHz)
//input wire host_CLK, // Microcom clock signal (8 MHz)
input wire Z80_M1n, // Z80 M1 - active LOW
input wire Z80_MREQn, // Z80 MREQ - active LOW
input wire Z80_WRn, // Z80 WR - active LOW
input wire Z80_RDn, // Z80 RD - active LOW
input wire [21:0] Z80_addr, // Microcom 22-bit address bus
input wire [7:0] Z80_wData, // Z80 DATA bus to pass incoming data to GPU RAM
input wire [7:0] gpu_rData,
input wire gpu_rd_rdy, // one-shot signal from mux that data is ready

// output
//output wire [7:0]  h_rd_data, // Z80 DATA bus to return data from GPU RAM to Z80
//output reg            h_rd_req, //
output reg gpu_wr_ena, // flag HIGH when writing to GPU RAM
output reg Z80_245data_dir, // control level converter direction for data flow
output reg [19:0]  gpu_addr, // connect to Z80_addr in vid_osd_generator to address GPU RAM
output reg [7:0]   gpu_wdata, // 8-bit data bus to GPU RAM in vid_osd_generator
output reg [7:0]   Z80_rData,
output reg Z80_rData_ena, // flag HIGH to write data back to Z80
output reg gpu_rd_req,
output reg Z80_245_oe // OE for 245 level translator (active LOW)

);

parameter MEMORY_RANGE = 3'b011; // Z80_addr[21:19] == 3'b011 targets the 512KB 'window' at 0x180000-0x1FFFFF
parameter DELAY_CYCLES = 1; // number of cycles to delay write for 245

wire mem_window, Z80_mreq, Z80_write, Z80_read, Z80_nRead, Write_GPU_RAM, Read_GPU_RAM, GPU_data_oe;

reg last_Z80_WR = 1'b0;  // keep these low on power-up, otherwise a read or write pulse may be triggered
reg last_Z80_RD = 1'b0;
reg data_hold = 1'b0; // used to latch the gpu_rd_rdy signal
reg [9:0] Z80_write_sequencer;

assign mem_window = (Z80_addr[21:19] == MEMORY_RANGE); // Define an active memory range
assign Z80_mreq = ~Z80_MREQn && Z80_M1n; // Define a bus memory access state
assign Z80_write = ~Z80_WRn && last_Z80_WR; // Isolate a single write transaction
assign Z80_read = ~Z80_RDn && last_Z80_RD; // Isolate a single read transaction
assign Z80_nRead = Z80_RDn && ~last_Z80_RD; // Isolate end of a read transaction

assign Write_GPU_RAM = mem_window && Z80_mreq && Z80_write; // Define a GPU Write action
assign Read_GPU_RAM = mem_window && Z80_mreq && Z80_read; // Define a GPU Read action
assign GPU_data_oe = mem_window && Z80_mreq && ~Z80_RDn; // Define the time the GPU ouputs data onto the Z80 data bus

assign gpu_rd_req = 1'b0; // default gpu_rd_req to LOW
assign Z80_245_oe = 1'b0; // disable 245 output
assign Z80_rData_ena = 1'b0; // set Z80 data pins to input on the FPGA

always @ (posedge GPU_CLK)
begin

Z80_write_sequencer[0] <= Write_GPU_RAM;
Z80_write_sequencer[9:1] <= Z80_write_sequencer[8:0];

if ( Z80_write_sequencer[0] )
Z80_245data_dir <= GPU_data_oe; // set 245 dir

if ( Z80_write_sequencer[0] )
Z80_rData_ena <= 1'b0; // set FPGA pins to input (should be by default)

if ( Z80_write_sequencer[0] )
Z80_245_oe <= 1'b1; // enable 245 output

if ( Z80_write_sequencer[DELAY_CYCLES] )
gpu_addr <= Z80_addr[18:0]; // latch address bus onto GPU address bus

if ( Z80_write_sequencer[DELAY_CYCLES] )
gpu_wdata <= Z80_wData; // latch data bus onto GPU data bus

if ( Z80_write_sequencer[DELAY_CYCLES] )
gpu_wr_ena <= 1'b1; // turn on FPGA RAM we

if ( Z80_write_sequencer[DELAY_CYCLES + 1] )
gpu_wr_ena <= 1'b0; // turn off FPGA RAM we

if ( Z80_write_sequencer[DELAY_CYCLES + 1] )
Z80_245_oe <= 1'b0; // disable 245 output


if (Read_GPU_RAM)
begin

gpu_addr <= Z80_addr[18:0];// pass address to GPU RAM
gpu_rd_req <= 1'b1; // flag a read request to the mux which is one-shotted in the mux
Z80_245data_dir <= GPU_data_oe; // set 245 direction (TO Z80)
Z80_245_oe <= 1'b1; // 'turn on' 245

end else if (gpu_rd_rdy) // gpu_rd_rdy is a one-shot from the mux, reset after one clock
begin

data_hold <= gpu_rd_rdy; // latch the gpu_rd_rdy signal to keep outputting data until Z80 is done with it
Z80_rData_ena <= 1'b1; // set bidir pins to output
gpu_rd_req <= 1'b0; // End the read request once the read is ready
Z80_rData[7:0] <= gpu_rData[7:0]; // Latch the GPU ram read into the output register for the Z80

end else if (data_hold && Z80_nRead)
begin

// data is being output to Z80, which has signalled end of read transaction
Z80_245_oe <= 1'b0; // disable 245 output
data_hold <= 1'b0; // reset the latch
Z80_rData_ena <= 1'b0; // set bidir pins to input

end

Z80_245data_dir      <= GPU_data_oe;
Z80_rData_ena <= GPU_data_oe;

last_Z80_WR <= Z80_WRn;
last_Z80_RD <= Z80_RDn;

end

endmodule
« Last Edit: November 24, 2019, 05:30:12 pm by nockieboy »
 

Offline Berni

  • Super Contributor
  • ***
  • Posts: 4957
  • Country: si
Re: FPGA VGA Controller for 8-bit computer
« Reply #466 on: November 24, 2019, 06:03:10 pm »
There are also specs for read-modify-write operations on the bus that could be used to transfer data in both directions simultaneously. As for multiple data on single address can be implemented by concatenating the data buses into one wide data bus. can be 1024 bits wide if needed. If multiple address and multiple data are needed the entire bus could be duplicated as many times as needed.
This really legitimizes Avalon capabilities...
So many getting into 16/32/64 bit designs always ignore whenever we want to write bytes in the middle only, but our memory necessarily doesn't have byte mask features.  Ir in the case of a GPU, maybe we are using 2 pixels per byte in 16 color mode and you want to hardware accelerate drawing on only one of the 2 pixels.  Read-modify-write, as well as any simple logic done to a pixel like a transparency stencil when copying blocks of memory becomes ever so useful.

Actually sorry i looked at the Avalon and Wishbone spec again and the read-modify-write operation is not simultaneous in both directions.

I never used it before as i didn't tend to need it. It turns out that the whole point of the read-modify-write cycle is to lock down any bus arbiters along the path so that you are guaranteed to get the last write part of the operation trough without someone else modifying that location and causing a glitch. This is not all that useful functionality in my opinion.

In theory you could set both write and read signals high on Avalon, but this is probably illegal in the spec?... somehow?...I dunno I haven't seen it specifically mentioned its not legal to do this, but i also haven't seen any timing diagrams that show doing this. Using common sense id say you are probably not supposed to do that. That being said tho most Avalon peripherals that only use internal memory usually have completely separate write logic and separate read logic. So it should work just fine to both read and write to one address, since the two separate sets of logic would simply do there thing as usual. But if you tried to pull this stunt on a memory controller for external memory it would probably refuse to do anything, or do just one of the two, since the data bus is bidirectional so it can't read and write at the same time. I suppose you could pretend this simultaneous read and write functionality is your own custom extension to the Avalon standard and so write your Avalon bus modules in a way where this is guaranteed to always work.

In any case even when using Wishbone or Avalon its good to have some idea what will be connected on the other end of the bus so that you can chose the port configuration that best fits the use. For just setting hardware registers the most basic configuration is good enugh. You want the bit depth of the bus to match up to avoid bus width adapters, if you have a 16 or 32bit bus you might want to support byte enable signals, if the device has a lot of latency you might want to support burst transfers...etc

Avalon also has a specification for streaming interfaces too. This is just a data bus with control signals and no address, usually used for point to point real time streaming of data like perhaps between a ADC controller and a DSP block. Useful for signal processing chains and all of Alteras IP blocks for filters and such use it. Again nothing special about it as there is no single one best way to design a streaming bus, but you can use the naming conventions and timings so that anyone who looks at it understands how the bus works.
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #467 on: November 24, 2019, 06:13:46 pm »

Okay, have done the above - updated code below.

Code: [Select]
module host_handler (

// input
input wire reset, // GPU reset signal
input wire GPU_CLK, // GPU clock (125 MHz)
//input wire host_CLK, // Microcom clock signal (8 MHz)
input wire Z80_M1n, // Z80 M1 - active LOW
input wire Z80_MREQn, // Z80 MREQ - active LOW
input wire Z80_WRn, // Z80 WR - active LOW
input wire Z80_RDn, // Z80 RD - active LOW
input wire [21:0] Z80_addr, // Microcom 22-bit address bus
input wire [7:0] Z80_wData, // Z80 DATA bus to pass incoming data to GPU RAM
input wire [7:0] gpu_rData,
input wire gpu_rd_rdy, // one-shot signal from mux that data is ready

// output
//output wire [7:0]  h_rd_data, // Z80 DATA bus to return data from GPU RAM to Z80
//output reg            h_rd_req, //
output reg gpu_wr_ena, // flag HIGH when writing to GPU RAM
output reg Z80_245data_dir, // control level converter direction for data flow
output reg [19:0]  gpu_addr, // connect to Z80_addr in vid_osd_generator to address GPU RAM
output reg [7:0]   gpu_wdata, // 8-bit data bus to GPU RAM in vid_osd_generator
output reg [7:0]   Z80_rData,
output reg Z80_rData_ena, // flag HIGH to write data back to Z80
output reg gpu_rd_req,
output reg Z80_245_oe // OE for 245 level translator (active LOW)

);

parameter MEMORY_RANGE = 3'b011; // Z80_addr[21:19] == 3'b011 targets the 512KB 'window' at 0x180000-0x1FFFFF
parameter DELAY_CYCLES = 1; // number of cycles to delay write for 245

wire mem_window, Z80_mreq, Z80_write, Z80_read, Z80_nRead, Write_GPU_RAM, Read_GPU_RAM, GPU_data_oe;

reg last_Z80_WR = 1'b0;  // keep these low on power-up, otherwise a read or write pulse may be triggered
reg last_Z80_RD = 1'b0;
reg data_hold = 1'b0; // used to latch the gpu_rd_rdy signal
reg [9:0] Z80_write_sequencer;

assign mem_window = (Z80_addr[21:19] == MEMORY_RANGE); // Define an active memory range
assign Z80_mreq = ~Z80_MREQn && Z80_M1n; // Define a bus memory access state
assign Z80_write = ~Z80_WRn && last_Z80_WR; // Isolate a single write transaction
assign Z80_read = ~Z80_RDn && last_Z80_RD; // Isolate a single read transaction
assign Z80_nRead = Z80_RDn && ~last_Z80_RD; // Isolate end of a read transaction

assign Write_GPU_RAM = mem_window && Z80_mreq && Z80_write; // Define a GPU Write action
assign Read_GPU_RAM = mem_window && Z80_mreq && Z80_read; // Define a GPU Read action
assign GPU_data_oe = mem_window && Z80_mreq && ~Z80_RDn; // Define the time the GPU ouputs data onto the Z80 data bus

assign gpu_rd_req = 1'b0; // default gpu_rd_req to LOW
assign Z80_245_oe = 1'b0; // disable 245 output
assign Z80_rData_ena = 1'b0; // set Z80 data pins to input on the FPGA

always @ (posedge GPU_CLK)
begin

Z80_write_sequencer[0] <= Write_GPU_RAM;
Z80_write_sequencer[9:1] <= Z80_write_sequencer[8:0];

if ( Z80_write_sequencer[0] )
Z80_245data_dir <= GPU_data_oe; // set 245 dir

if ( Z80_write_sequencer[0] )
Z80_rData_ena <= 1'b0; // set FPGA pins to input (should be by default)

if ( Z80_write_sequencer[0] )
Z80_245_oe <= 1'b1; // enable 245 output

if ( Z80_write_sequencer[DELAY_CYCLES] )
gpu_addr <= Z80_addr[18:0]; // latch address bus onto GPU address bus

if ( Z80_write_sequencer[DELAY_CYCLES] )
gpu_wdata <= Z80_wData; // latch data bus onto GPU data bus

if ( Z80_write_sequencer[DELAY_CYCLES] )
gpu_wr_ena <= 1'b1; // turn on FPGA RAM we

if ( Z80_write_sequencer[DELAY_CYCLES + 1] )
gpu_wr_ena <= 1'b0; // turn off FPGA RAM we

if ( Z80_write_sequencer[DELAY_CYCLES + 1] )
Z80_245_oe <= 1'b0; // disable 245 output


if (Read_GPU_RAM)
begin

gpu_addr <= Z80_addr[18:0];// pass address to GPU RAM
gpu_rd_req <= 1'b1; // flag a read request to the mux which is one-shotted in the mux
Z80_245data_dir <= GPU_data_oe; // set 245 direction (TO Z80)
Z80_245_oe <= 1'b1; // 'turn on' 245

end else if (gpu_rd_rdy) // gpu_rd_rdy is a one-shot from the mux, reset after one clock
begin

data_hold <= gpu_rd_rdy; // latch the gpu_rd_rdy signal to keep outputting data until Z80 is done with it
Z80_rData_ena <= 1'b1; // set bidir pins to output
gpu_rd_req <= 1'b0; // End the read request once the read is ready
Z80_rData[7:0] <= gpu_rData[7:0]; // Latch the GPU ram read into the output register for the Z80

end else if (data_hold && Z80_nRead)
begin

// data is being output to Z80, which has signalled end of read transaction
Z80_245_oe <= 1'b0; // disable 245 output
data_hold <= 1'b0; // reset the latch
Z80_rData_ena <= 1'b0; // set bidir pins to input

end

Z80_245data_dir      <= GPU_data_oe;
Z80_rData_ena <= GPU_data_oe;

last_Z80_WR <= Z80_WRn;
last_Z80_RD <= Z80_RDn;

end

endmodule


Ok, much better.  Now, looking at it this way:
Code: [Select]
module Z80_bridge (

// input
input wire reset, // GPU reset signal
input wire GPU_CLK, // GPU clock (125 MHz)
//input wire host_CLK, // Microcom clock signal (8 MHz)
input wire Z80_M1n, // Z80 M1 - active LOW
input wire Z80_MREQn, // Z80 MREQ - active LOW
input wire Z80_WRn, // Z80 WR - active LOW
input wire Z80_RDn, // Z80 RD - active LOW
input wire [21:0] Z80_addr, // Microcom 22-bit address bus
input wire [7:0] Z80_wData, // Z80 DATA bus to pass incoming data to GPU RAM
input wire [7:0] gpu_rData,
input wire gpu_rd_rdy, // one-shot signal from mux that data is ready

// output
//output wire [7:0]  h_rd_data, // Z80 DATA bus to return data from GPU RAM to Z80
//output reg            h_rd_req, //
output reg gpu_wr_ena, // flag HIGH when writing to GPU RAM
output reg Z80_245data_dir, // control level converter direction for data flow
output reg [19:0]  gpu_addr, // connect to Z80_addr in vid_osd_generator to address GPU RAM
output reg [7:0]   gpu_wdata, // 8-bit data bus to GPU RAM in vid_osd_generator
output reg [7:0]   Z80_rData,
output reg Z80_rData_ena, // flag HIGH to write data back to Z80
output reg gpu_rd_req,
output reg Z80_245_oe // OE for 245 level translator (active LOW)

);

parameter MEMORY_RANGE = 3'b011; // Z80_addr[21:19] == 3'b011 targets the 512KB 'window' at 0x180000-0x1FFFFF
parameter DELAY_CYCLES = 2; // number of cycles to delay write for 245

wire mem_window, Z80_mreq, Z80_write, Z80_read, Z80_nRead, Write_GPU_RAM, Read_GPU_RAM, GPU_data_oe;

reg last_Z80_WR = 1'b0;  // keep these low on power-up, otherwise a read or write pulse may be triggered
reg last_Z80_RD = 1'b0;
reg data_hold = 1'b0; // used to latch the gpu_rd_rdy signal
reg [9:0] Z80_write_sequencer;

assign mem_window = (Z80_addr[21:19] == MEMORY_RANGE); // Define an active memory range
assign Z80_mreq = ~Z80_MREQn && Z80_M1n; // Define a bus memory access state
assign Z80_write = ~Z80_WRn && last_Z80_WR; // Isolate a single write transaction
assign Z80_read = ~Z80_RDn && last_Z80_RD; // Isolate a single read transaction
assign Z80_nRead = Z80_RDn && ~last_Z80_RD; // Isolate end of a read transaction

assign Write_GPU_RAM = mem_window && Z80_mreq && Z80_write; // Define a GPU Write action
assign Read_GPU_RAM = mem_window && Z80_mreq && Z80_read; // Define a GPU Read action
assign GPU_data_oe = mem_window && Z80_mreq && ~Z80_RDn; // Define the time the GPU ouputs data onto the Z80 data bus

assign gpu_rd_req = 1'b0; // default gpu_rd_req to LOW
assign Z80_245_oe = 1'b0; // disable 245 output
assign Z80_rData_ena = 1'b0; // set Z80 data pins to input on the FPGA

always @ (posedge GPU_CLK)
begin

Z80_write_sequencer[9:0] <= { Z80_write_sequencer[8:0] , Write_GPU_RAM };


if ( Z80_write_sequencer[0] )                 Z80_245data_dir  <= GPU_data_oe;   // set 245 dir
if ( Z80_write_sequencer[0] )                 Z80_rData_ena    <= 1'b0;           // set FPGA pins to input (should be by default)

if ( Z80_write_sequencer[1] )                 Z80_245_oe       <= 1'b1;           // enable 245 output

if ( Z80_write_sequencer[DELAY_CYCLES + 1] )  gpu_addr         <= Z80_addr[18:0]; // latch address bus onto GPU address bus
if ( Z80_write_sequencer[DELAY_CYCLES + 1] )  gpu_wdata        <= Z80_wData;      // latch data bus onto GPU data bus
if ( Z80_write_sequencer[DELAY_CYCLES + 1] )  gpu_wr_ena       <= 1'b1;           // turn on FPGA RAM we

if ( Z80_write_sequencer[DELAY_CYCLES + 2] )  gpu_wr_ena       <= 1'b0;           // turn off FPGA RAM we
if ( Z80_write_sequencer[DELAY_CYCLES + 2] )  Z80_245_oe       <= 1'b0;           // disable 245 output



if (Read_GPU_RAM)
begin

gpu_addr <= Z80_addr[18:0];// pass address to GPU RAM
gpu_rd_req <= 1'b1; // flag a read request to the mux which is one-shotted in the mux
Z80_245data_dir <= GPU_data_oe; // set 245 direction (TO Z80)
Z80_245_oe <= 1'b1; // 'turn on' 245

end else if (gpu_rd_rdy) // gpu_rd_rdy is a one-shot from the mux, reset after one clock
begin

data_hold <= gpu_rd_rdy; // latch the gpu_rd_rdy signal to keep outputting data until Z80 is done with it
Z80_rData_ena <= 1'b1; // set bidir pins to output
gpu_rd_req <= 1'b0; // End the read request once the read is ready
Z80_rData[7:0] <= gpu_rData[7:0]; // Latch the GPU ram read into the output register for the Z80

end else if (data_hold && Z80_nRead)
begin

// data is being output to Z80, which has signalled end of read transaction
Z80_245_oe <= 1'b0; // disable 245 output
data_hold <= 1'b0; // reset the latch
Z80_rData_ena <= 1'b0; // set bidir pins to input

end

Z80_245data_dir      <= GPU_data_oe;
Z80_rData_ena <= GPU_data_oe;

last_Z80_WR <= Z80_WRn;
last_Z80_RD <= Z80_RDn;

end

endmodule


See how much easier it is for me, or even you to move around the timing / or sequence.  Plus, you need to add an additional clock cycle on top of the delay generated by the 245's OE control.  Remember, there is also a 'tsu' and 'th' (Time setup, and time hold for data inputs with relation to external and internal FPGA clock) figure inside the FPGA which must be met, and, if you are not defining these for your inputs in the compiler and relying on the compiler's 'FMAX' alone, you need to assume the the inputs take 1 entire clock cycle before they get from the input pins to the internal registers.  Operating the FPGA IO's at break-neck speed would require you to set these figure up correctly so the compiler will tell you if your design will receive the correct data, however, with this design, you can easily get away with that cheap 1 additional clock cycle delay as you are running more than 10 clock cycles within a single Z80 clock cycle.  And even then, the Z80 is even holding that data for even longer than 1 clock cycle...

Basically, your time is the switch on delay of the 245, under worst case conditions, not optimum, plus capacitance and trace leads, plus the delay from input pin in the FPGA to it's gpu_wdata[7:0] register.
« Last Edit: November 24, 2019, 06:27:50 pm by BrianHG »
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #468 on: November 24, 2019, 06:36:56 pm »
Error:
------------------------------------------------------------------------------------------------
   if ( Z80_write_sequencer[0] )                 Z80_245data_dir  <= GPU_data_oe;     // set 245 dir
------------------------------------------------------------------------------------------------
and this:
------------------------------------------------------------------------------------------------
if (Read_GPU_RAM)
   begin
      
      gpu_addr      <= Z80_addr[18:0];// pass address to GPU RAM
      gpu_rd_req   <= 1'b1;            // flag a read request to the mux which is one-shotted in the mux
      Z80_245data_dir      <= GPU_data_oe;   // set 245 direction (TO Z80)
------------------------------------------------------------------------------------------------

Should be set to exclusively 1's or 0's, or defined by a parameter if you haven't decided the direction of the IC on your PCB.

The read section could use a little cleaning.  I cant follow it clearly.  It's going to be time to simulate this one to double check things out...


 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #469 on: November 24, 2019, 06:56:55 pm »
See how much easier it is for me, or even you to move around the timing / or sequence.

Yeah, I suppose so - I guess it would be possible to re-write the read if...endif conditional in the same way, but the read flow depends on a couple of different triggers which, I guess, would make the pipeline method undesirable?

Plus, you need to add an additional clock cycle on top of the delay generated by the 245's OE control.  Remember, there is also a 'tsu' and 'th' (Time setup, and time hold for data inputs with relation to external and internal FPGA clock) figure inside the FPGA which must be met, and, if you are not defining these for your inputs in the compiler and relying on the compiler's 'FMAX' alone, you need to assume the the inputs take 1 entire clock cycle before they get from the input pins to the internal registers.  Operating the FPGA IO's at break-neck speed would require you to set these figure up correctly so the compiler will tell you if your design will receive the correct data, however, with this design, you can easily get away with that cheap 1 additional clock cycle delay as you are running more than 10 clock cycles within a single Z80 clock cycle.  And even then, the Z80 is even holding that data for even longer than 1 clock cycle...

Basically, your time is the switch on delay of the 245, under worst case conditions, not optimum, plus capacitance and trace leads, plus the delay from input pin in the FPGA to it's gpu_wdata[7:0] register.

Okay, fair enough - so it's looking like the Z80_bridge module is pretty much complete now?  Time to start simulating...?  :-BROKE

Error:
------------------------------------------------------------------------------------------------
   if ( Z80_write_sequencer[0] )                 Z80_245data_dir  <= GPU_data_oe;     // set 245 dir
------------------------------------------------------------------------------------------------
and this:
------------------------------------------------------------------------------------------------
if (Read_GPU_RAM)
   begin
      
      gpu_addr      <= Z80_addr[18:0];// pass address to GPU RAM
      gpu_rd_req   <= 1'b1;            // flag a read request to the mux which is one-shotted in the mux
      Z80_245data_dir      <= GPU_data_oe;   // set 245 direction (TO Z80)
------------------------------------------------------------------------------------------------

Should be set to exclusively 1's or 0's, or defined by a parameter if you haven't decided the direction of the IC on your PCB.

Sorted.  I've also removed these two lines:
------------------------------------------------------------------------------------------------
   Z80_245data_dir   <= GPU_data_oe;
   Z80_rData_ena      <= GPU_data_oe;
------------------------------------------------------------------------------------------------
Both those outputs are fully catered for in the write and read code, so I've commented them out.

The read section could use a little cleaning.  I cant follow it clearly.  It's going to be time to simulate this one to double check things out...

Code: [Select]
if (Read_GPU_RAM)
begin

gpu_addr <= Z80_addr[18:0];// pass address to GPU RAM
gpu_rd_req <= 1'b1; // flag a read request to the mux which is one-shotted in the mux
Z80_245data_dir <= 1'b0; // set 245 direction (TO Z80)
Z80_245_oe <= 1'b1; // 'turn on' 245

So when a Z80 read is detected, we grab the bottom 19 bits of the address and pass that to the mux, along with the gpu_rd_req.  The 245's direction is set TO Z80 and its output is enabled as the Z80 won't be driving the data bus at all during the read operation, and will only sample the data bus at the end of the read op.

Code: [Select]
end else if (gpu_rd_rdy) // gpu_rd_rdy is a one-shot from the mux, reset after one clock
begin

data_hold <= 1'b0; // latch the gpu_rd_rdy signal to keep outputting data until Z80 is done with it
Z80_rData_ena <= 1'b1; // set bidir pins to output
gpu_rd_req <= 1'b0; // End the read request once the read is ready
Z80_rData[7:0] <= gpu_rData[7:0]; // Latch the GPU ram read into the output register for the Z80

So the mux will signal via gpu_rd_rdy for one clock cycle that the GPU RAM data is ready.  When this is detected, data_hold goes HIGH to keep the data output from the FPGA to the Z80, Z80_rData_ena goes HIGH to set bidir pins to output, gpu_rd_req is reset and the data is latched onto the Z80_rData bus.

Code: [Select]
end else if (data_hold && Z80_nRead)
begin

// data is being output to Z80, which has signalled end of read transaction
Z80_245_oe <= 1'b0; // disable 245 output
data_hold <= 1'b0; // reset the latch
Z80_rData_ena <= 1'b0; // set bidir pins to input

end

Finally, when the end of the Z80 read cycle is detected by RD going high, the 245's output is disabled, the data_hold latch is reset and the bidir pins are set to input.
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #470 on: November 24, 2019, 08:31:40 pm »
Arrrg, that ' 1 shot ' on the read is nasty looking at the timing chart.  If seem you will need to qualify it on the rising edge of the CPU clock unlike the Z80 write data where we can cheat and trigger on the fall of the Z80 WE exclusively.

Here is how we will need to assign and trigger the read:


assign Read_GPU_RAM_BEGIN   = mem_window && Z80_mreq && ~Z80_RDn && Z80_clk && ~Z80_clk_delay  ;   // Define the beginning of a Z80 read request of GPU Ram.
assign Read_GPU_RAM_END   = Z80_RDn && ~last_Z80_RD;  // Define the time to end a GPU ram read

........
Z80_clk_delay <= Z80_clk;  // add this to find the rising clock edge.


you only need 2 IF()', 1 for Read_GPU_RAM_BEGIN, and 1 for Read_GPU_RAM_END.

This change must be made since we cannot guarantee which signal will fall first, the Z80_MREQn, or the Z80_RDn.  In the original code, if the 'Z80_RDn' falls before the 'Z80_MREQn', we will not see that cycle as a read & we will ignore the transaction.  We only know that both signals are valid by the time the next rising edge of the CPU clk.  To be constant, you may also qualify the write signal strobe the same way as well.


As for the 'Z80_rData', make 1 if outside everything else an just say:
if (gpu_rd_rdy)  Z80_rData[7:0]   <= gpu_rData[7:0];   // Latch the GPU ram read into the output register for the Z80

And your done...
« Last Edit: November 24, 2019, 08:36:40 pm by BrianHG »
 

Offline nockieboyTopic starter

  • Super Contributor
  • ***
  • Posts: 1812
  • Country: england
Re: FPGA VGA Controller for 8-bit computer
« Reply #471 on: November 24, 2019, 10:47:22 pm »
Arrrg, that ' 1 shot ' on the read is nasty looking at the timing chart.  If seem you will need to qualify it on the rising edge of the CPU clock unlike the Z80 write data where we can cheat and trigger on the fall of the Z80 WE exclusively.

Here is how we will need to assign and trigger the read:


assign Read_GPU_RAM_BEGIN   = mem_window && Z80_mreq && ~Z80_RDn && Z80_clk && ~Z80_clk_delay  ;   // Define the beginning of a Z80 read request of GPU Ram.
assign Read_GPU_RAM_END   = Z80_RDn && ~last_Z80_RD;  // Define the time to end a GPU ram read

........
Z80_clk_delay <= Z80_clk;  // add this to find the rising clock edge.


you only need 2 IF()', 1 for Read_GPU_RAM_BEGIN, and 1 for Read_GPU_RAM_END.

This change must be made since we cannot guarantee which signal will fall first, the Z80_MREQn, or the Z80_RDn.  In the original code, if the 'Z80_RDn' falls before the 'Z80_MREQn', we will not see that cycle as a read & we will ignore the transaction.  We only know that both signals are valid by the time the next rising edge of the CPU clk.  To be constant, you may also qualify the write signal strobe the same way as well.


As for the 'Z80_rData', make 1 if outside everything else an just say:
if (gpu_rd_rdy)  Z80_rData[7:0]   <= gpu_rData[7:0];   // Latch the GPU ram read into the output register for the Z80

And your done...

Okay, I think I've made the changes required above.  I've commented-out changes to data_hold as it appears it's no longer necessary.

Code: [Select]
module Z80_bridge (

// input
input wire reset, // GPU reset signal
input wire GPU_CLK, // GPU clock (125 MHz)
input wire Z80_CLK, // Microcom clock signal (8 MHz)
input wire Z80_M1n, // Z80 M1 - active LOW
input wire Z80_MREQn, // Z80 MREQ - active LOW
input wire Z80_WRn, // Z80 WR - active LOW
input wire Z80_RDn, // Z80 RD - active LOW
input wire [21:0] Z80_addr, // Microcom 22-bit address bus
input wire [7:0] Z80_wData, // Z80 DATA bus to pass incoming data to GPU RAM
input wire [7:0] gpu_rData,
input wire gpu_rd_rdy, // one-shot signal from mux that data is ready

// output
//output wire [7:0]  h_rd_data, // Z80 DATA bus to return data from GPU RAM to Z80
//output reg            h_rd_req, //
output reg gpu_wr_ena, // flag HIGH when writing to GPU RAM
output reg Z80_245data_dir, // control level converter direction for data flow - HIGH = A -> B (toward FPGA)
output reg [19:0]  gpu_addr, // connect to Z80_addr in vid_osd_generator to address GPU RAM
output reg [7:0]   gpu_wdata, // 8-bit data bus to GPU RAM in vid_osd_generator
output reg [7:0]   Z80_rData,
output reg Z80_rData_ena, // flag HIGH to write data back to Z80
output reg gpu_rd_req,
output reg Z80_245_oe // OE for 245 level translator (active LOW)

);

parameter MEMORY_RANGE = 3'b011; // Z80_addr[21:19] == 3'b011 targets the 512KB 'window' at 0x180000-0x1FFFFF
parameter DELAY_CYCLES = 2; // number of cycles to delay write for 245

wire mem_window, Z80_mreq, Z80_write, Z80_read, Z80_nRead, Write_GPU_RAM, Read_GPU_RAM, GPU_data_oe, Z80_clk_delay;
wire Read_GPU_RAM_BEGIN, Read_GPU_RAM_END;

reg last_Z80_WR = 1'b0;  // keep these low on power-up, otherwise a read or write pulse may be triggered
reg last_Z80_RD = 1'b0;
//reg data_hold = 1'b0; // used to latch the gpu_rd_rdy signal
reg [9:0] Z80_write_sequencer;

assign mem_window = (Z80_addr[21:19] == MEMORY_RANGE); // Define an active memory range
assign Z80_mreq = ~Z80_MREQn && Z80_M1n; // Define a bus memory access state
assign Z80_write = ~Z80_WRn && last_Z80_WR; // Isolate a single write transaction
assign Z80_read = ~Z80_RDn && last_Z80_RD; // Isolate a single read transaction
assign Z80_nRead = Z80_RDn && ~last_Z80_RD; // Isolate end of a read transaction

assign Write_GPU_RAM = mem_window && Z80_mreq && Z80_write; // Define a GPU Write action
//assign Read_GPU_RAM = mem_window && Z80_mreq && Z80_read; // Define a GPU Read action
assign Read_GPU_RAM_BEGIN = mem_window && Z80_mreq && ~Z80_RDn && Z80_CLK && ~Z80_clk_delay;   // Define the beginning of a Z80 read request of GPU Ram.
assign Read_GPU_RAM_END = Z80_RDn && ~last_Z80_RD;  // Define the time to end a GPU ram read
assign GPU_data_oe = mem_window && Z80_mreq && ~Z80_RDn; // Define the time the GPU ouputs data onto the Z80 data bus

assign gpu_rd_req = 1'b0; // default gpu_rd_req to LOW
assign Z80_245_oe = 1'b0; // disable 245 output
assign Z80_rData_ena = 1'b0; // set Z80 data pins to input on the FPGA

always @ (posedge GPU_CLK)
begin

Z80_write_sequencer[9:0] <= { Z80_write_sequencer[8:0], Write_GPU_RAM };

if ( Z80_write_sequencer[0] )                 Z80_245data_dir  <= 1'b1; // set 245 dir toward FPGA
if ( Z80_write_sequencer[0] )                 Z80_rData_ena    <= 1'b0; // set FPGA pins to input (should be by default)

if ( Z80_write_sequencer[1] )                 Z80_245_oe       <= 1'b1; // enable 245 output

if ( Z80_write_sequencer[DELAY_CYCLES + 1] )  gpu_addr         <= Z80_addr[18:0]; // latch address bus onto GPU address bus
if ( Z80_write_sequencer[DELAY_CYCLES + 1] )  gpu_wdata        <= Z80_wData; // latch data bus onto GPU data bus
if ( Z80_write_sequencer[DELAY_CYCLES + 1] )  gpu_wr_ena       <= 1'b1; // turn on FPGA RAM we

if ( Z80_write_sequencer[DELAY_CYCLES + 2] )  gpu_wr_ena       <= 1'b0; // turn off FPGA RAM we
if ( Z80_write_sequencer[DELAY_CYCLES + 2] )  Z80_245_oe       <= 1'b0; // disable 245 output

if ( Read_GPU_RAM_BEGIN )
begin
gpu_addr <= Z80_addr[18:0];// pass address to GPU RAM
gpu_rd_req <= 1'b1; // flag a read request to the mux which is one-shotted in the mux
Z80_245data_dir <= 1'b0; // set 245 direction (TO Z80)
Z80_245_oe <= 1'b1; // enable 245 output
end

if ( gpu_rd_rdy ) // gpu_rd_rdy is a one-shot from the mux, reset after one clock
begin

//data_hold <= 1'b1; // latch the gpu_rd_rdy signal to keep outputting data until Z80 is done with it
Z80_rData_ena <= 1'b1; // set bidir pins to output
gpu_rd_req <= 1'b0; // End the read request once the read is ready
Z80_rData[7:0] <= gpu_rData[7:0]; // Latch the GPU RAM read into the output register for the Z80

end

if ( Read_GPU_RAM_END )
begin
// data is being output to Z80, which has signalled end of read transaction
Z80_245_oe <= 1'b0; // disable 245 output
//data_hold <= 1'b0; // reset the latch
Z80_rData_ena <= 1'b0; // set bidir pins to input
end

//Z80_245data_dir <= GPU_data_oe;
//Z80_rData_ena <= GPU_data_oe;

last_Z80_WR <= Z80_WRn;
last_Z80_RD <= Z80_RDn;
Z80_clk_delay <= Z80_CLK;  // find the rising clock edge

end

endmodule


If that's all looking good, I'll get started on setting up a simulation for it tomorrow if I get the time.  What sort of complexity will the mux bring?  Will this be a cut-down version of the multiport_GPU_RAM module?
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #472 on: November 24, 2019, 11:27:32 pm »
Looks Good.
The mux is a 2:1, or 3:1 lateron with the cpu on top and RS232 debugger on bottom.

Other than making that Z80 module's read req into a single pulse instead of always on, the mux will cycle to any available triggered input.  Just a J-K flipflop equivalent selecting 2 address and data sourced, with a 2 clock cycle delayed rd_ready output matching the altsynccram megafunction's 2 clock cycle read delay.

 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #473 on: November 26, 2019, 12:49:16 am »
Ok, @nockieboy, now that I know you can simulate code in Quartus, let's take a look at point #3 here:
https://www.eevblog.com/forum/fpga/fpga-vga-controller-for-8-bit-computer/msg2788698/#msg2788698

Now I said I would like to a Verilog guide about initiating memory : https://www.chipverify.com/verilog/verilog-single-port-ram , but I don't like their example.  We only need a block of registers to write to and have continuous parallel access with some instantaneous reset values.  So, take a look at this I threw together:

Code: [Select]
module r_bank (
input wire rst,
input wire clk,
input wire we,
input wire [7:0] addr_in,
input wire [7:0] data_in,

output reg  [7:0] mem[256]  );


 
  integer i;
 
  always @ (posedge clk) begin
 
  if (rst) begin

mem[0] <= 8'hAA;
mem[1] <= 8'hBB;
mem[2] <= 8'hCC;
mem[3] <= 8'hDD;

    for (i = 4; i < 256; i = i + 1) begin
mem[i] <= 8'h0;
    end

end else begin
 
if (we)
mem[addr_in] <= data_in;
end

end

endmodule

Now, you will need to re-work it to the specs and controls I laid out for your GPU.  One thing is in Quartus, to use Verilog addressable memory arrays, please set the 'Verilog' language style/version to 'System Verilog'.  Also, to make the block diagram symbol for this example verilog code, you will need to use Quartus Prime as old QuartusIIv9.1 doesn't know how to do it.  Then you can bring that .bsf file back into the older Quartus and simulate it.

Here is an example of what it should look like.



You can see how Altera/Intel labels the ' register memory bank ' differently outside the Verilog module where I fed some of the individual register bytes to a few output pins.
This bank of registers is what will hold and feed all you graphics control settings.  As you can see, you write to them just like ram, but, you have individual access to all their contents at all times.

It's also much reads much easier this way than mem[255*8+7:0*0], and selecting the right sub wires, though, it works out to the same thing.
« Last Edit: November 26, 2019, 03:23:17 am by BrianHG »
 

Offline BrianHG

  • Super Contributor
  • ***
  • Posts: 7747
  • Country: ca
Re: FPGA VGA Controller for 8-bit computer
« Reply #474 on: November 26, 2019, 02:17:43 am »
The RS232_Debugger I created for nockieboy's VGA Controller has become a thread upon it's own here as it has proven functional and has also become a viable product all on it's own:
https://www.eevblog.com/forum/fpga/verilog-rs232-uart-and-rs232-debugger-source-code-and-educational-tutorial/msg2801388/#msg2801388

The latest RS232_Debugger.exe HEX editor/filed editor is now located here:
https://www.eevblog.com/forum/fpga/verilog-rs232-uart-and-rs232-debugger-source-code-and-educational-tutorial/?action=dlattach;attach=878982
 
The following users thanked this post: nockieboy


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf