ULA revistited.

Any discussions related to the creation of new hardware or software for the ZX80 or ZX81
User avatar
Paul
Posts: 1511
Joined: Thu May 27, 2010 8:15 am
Location: Germanys west end

Re: ULA revistited.

Post by Paul »

Prime wrote: Yep :) Pretty much designed one night, exposed-developed-etched then drilled and soldered th next day :)
Can you (please) attach some pictures?
In theory, there is no difference between theory and practice. But, in practice, there is.
Prime
Posts: 134
Joined: Thu Mar 24, 2011 3:02 am

Re: ULA revistited.

Post by Prime »

Paul wrote:
Prime wrote: Yep :) Pretty much designed one night, exposed-developed-etched then drilled and soldered th next day :)
Can you (please) attach some pictures?
I tried but they where too big :( I uploaded them here : http://protein.bio.warwick.ac.uk/~bshu/ZX81-ULA/

Phill.
User avatar
Paul
Posts: 1511
Joined: Thu May 27, 2010 8:15 am
Location: Germanys west end

Re: ULA revistited.

Post by Paul »

Thanks, it looks great.
This starts another lot of questions.
Will this be able to fit into the original Case?
Would there be space for some extensions like ps/2 Keyboard-interface?
A Monitor output would be easy I think.
Could it be adapted to drive vga-Monitors?
Could monitor output be "independent" from CPU-Speed? I know nothing about FPGA, but could a FPGA generate the Video-Output without the need for the Z80 to take over this part keeping the ZX81 in "fast" mode by giving the Z80 waitstaites whenever it wants to access the RAM while the FPGA needs it for video?
I read about a fpga based ULA for the Spectrum that does "doublescan", whatever this means :oops:
In theory, there is no difference between theory and practice. But, in practice, there is.
Prime
Posts: 134
Joined: Thu Mar 24, 2011 3:02 am

Re: ULA revistited.

Post by Prime »

Paul wrote:Thanks, it looks great.
This starts another lot of questions.
Will this be able to fit into the original Case?
The current hardware cannot, as the PLCC CPLD is too big, however, the same CPLDs are available in surface mount packaging which would be much thinner, but hardwer to solder, so it can be re-designed to use one of these, but as I already had the PLCC chips it seemed sensible to use those for prototyping....
Would there be space for some extensions like ps/2 Keyboard-interface?
Probably not, that would be beter suited to a microcontroler anyway, something like the PIC on the ZXPAND.....I may have a go at a PS/2 interface using an AVR as I already have the PS/2 code written so that's half the battle won.... If SirMorris wants to try and put that functionality in ZXPAND, then feel free to poke me for the PS/2 code.
A Monitor output would be easy I think.
Composite mono would just need a small signal npn and a couple of components, actually you might be able to get away with using the CPLD output directly, I'll do some experements.
Could it be adapted to drive vga-Monitors?
Could monitor output be "independent" from CPU-Speed? I know nothing about FPGA, but could a FPGA generate the Video-Output without the need for the Z80 to take over this part keeping the ZX81 in "fast" mode by giving the Z80 waitstaites whenever it wants to access the RAM while the FPGA needs it for video?
I read about a fpga based ULA for the Spectrum that does "doublescan", whatever this means :oops:
VGA and speed independence would be hard to do without re-writing the ZX81's display routines, and at that point you would probably break compatibility with things like the software based hires programs. About the only way I can think of that this could be made to work, would be to use the current 25fps scanning to buffer the bitmaps into a ram buffer and then have this buffer read out at VGA frequencies.

Cheers.

Phill.
User avatar
Andy Rea
Posts: 1606
Joined: Fri May 09, 2008 2:48 pm
Location: Planet Earth
Contact:

Re: ULA revistited.

Post by Andy Rea »

excellent stuff, with the right sized CPLD it and the osccilator (could do that in SMD too ) should easily fit on a board barely bigger than the 40 pin socket it's going to plug into.

It's probably best to leave things like ps/2 keyboard intefaces, and the like as external add-ons, as for driving a VGA monitor i think the only way is like Phil says, which i believe is pretty much how the boxes you can buy work.

regards Andy.
what's that Smell.... smells like fresh flux and solder fumes...
sirmorris
Posts: 2811
Joined: Thu May 08, 2008 5:45 pm

Re: ULA revistited.

Post by sirmorris »

Prime wrote:I checked by searching on the web and apparently defining it 8'bz should make it open collector / drain, so it should work. Humm I may try and see if I can design a JS/KBD interface to test that.... humm wonder if there are any schematics of such a device out there ?
I built the keyboard portion of this and it worked well.

http://www.user.dccnet.com/wrigter/inde ... XKBDv3.htm
Prime
Posts: 134
Joined: Thu Mar 24, 2011 3:02 am

Re: ULA revistited.

Post by Prime »

Progress so far :

Emulation of Sinclair ULA seems to be working correctly as far as I can test, there is an issue with tape input levels, but I think a single transistor amp might fix that.
Reading of the BNM.<sp> row at startup / reset works correctly.
Setting of border / paper colour works correctly.

M1 not is not yet implemented, but I see no problem implementing this.
Clock doubling is coded but I can't get it working, scoping the relevant debugging outputs on PortB, it seems to work for a few cycles and then fails.

Current Verilog code :

Code: Select all

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 		Ramoth
// Engineer: 		Phill.Harvey-Smith, based on pervious work by Andy Rea
// 
// Create Date:   17:52:53 03/24/2011 
// Design Name: 	ZX81 ULA replacement	
// Module Name:   ULA 
// Project Name: 
// Target Devices: XC9572 / XC95108
// Tool versions: 
// Description: 	To serve as a replacement for the ULA in the ZX81 computer.
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
// I have used the Acorn convention of prefixing active low outputs and inputs to
// che design with a lower case n so for example nRD would be an active loaw read 
// strobe
// 
// 2011-03-30 WooHoo it's working !
//
// 2011-04-04 
//		At the sugestion of Andy Rea, sample the first read of the keys B to Space
//		and use this to configure extra features.
//		Currently enabled are :
//		bit	key	purpose
//		0		Space	Black border
//		1		.		Black paper, white ink
//		2		M		M1 not enabled
//		3		N		Clock double enabled
//					
//////////////////////////////////////////////////////////////////////////////////
module ULA(
    inout [7:0] 	Data,			// Databus from Z80
    inout [15:14] AddrH,		// Two parts of Address bus
    inout [8:0] 	AddrL,
    
	 input 			nHALT,		// Halt from Z80
    input			nRD,			// Read and write strobes from Z80
	 input			nWR,
    input 			nIORQ,		// IO request from Z80
    input 			nMREQ,		// Memory request from Z80
    input 			nM1,			// M1 from Z80
    input 			USAUK,		// USA or UK jumper
	 
	 input 			CLKIN,		// 6.5MHz clock input signal
    input 			TapeIn,		// Input from cassette when loading
    input [4:0] 	Kbd,			// Keyboard row inputs
    
    output 			nNMI,
	 output 			CLKOUT,		// 3.5Mhz clock output to Z80 
    output 			nROMCS,		// ROP chip select signal
    output 			nRAMCS,		// RAM chip select signal
    output 			VidOut,		// Video output signal 
    output 			Blank,		// Video blanking signal
    output 			CSync,		// Composite Sync
	 
	 // Debugging ports
	 inout [7:0]	PortA,
	 inout [7:0]	PortB,
	 inout [7:0]	PortC
    );

// Local registers

	reg	[7:0]		HCount;		// HSync Counter 	
	reg 	[8:0] 	VCount;		// Vertical counter
	reg	[3:0]		Micro;		// Micro counter
	reg	[7:0]		ShiftReg;	// Video shift register
	reg	CLK325;					// 3.25MHz clock
	reg 	VSync;					// Vertical syncronisation pulse
	reg 	NMIon;					// NMI on flag
	reg	KeysLatched	= 1'b0;	// True after keys have been latched
	reg	[4:0]		Keys;			// Latched keys
	reg	TurboEnabled;			// Is turbo currently enabled ?
	reg	TurboOnOff;				// Temp flag set by out instructions

// Assign aliases for latched keys, and subsequently use aliases, that way
// if we decide to change the order of the bits, then we only have to do it 
// in one place in the code.

	assign BorderColour	= !Keys[0];
	assign PaperColour	= !Keys[1];
	assign M1NotFlag		= Keys[2];
	assign TurboFlag		= Keys[3];	
	
	assign nReset	= PortA[0];
	
	// IO read and write strobes
	assign nIORD	= nRD | nIORQ;
	assign nIOWR	= nWR | nIORQ;

	// Clock generator output clock to Z80 (3.25MHz), is generated by dividing input clock
	// 6.5MHz by 2.
	always @(negedge CLKIN)
	begin
 		CLK325 <= !CLK325;
	end
	
	// Aliases just for convenience
	assign CLK65	= CLKIN;
	assign nCLK65	= !CLKIN;
	
	// Assign Clock to Z80 depending on current turbo setting.
	assign Z80CLK 	= TurboEnabled ? CLK65 : CLK325;
	
	assign CLKOUT	= Z80CLK;
	
	// We should only change TurboEnabled when *BOTH* clocks are low
	assign TurboEnableClk	= (!CLK65 & !CLK325 & TurboFlag & nReset);
	
	// Latch current turbo setting syncronously with clocks being low, if 
	// reset is low, then set Turbo off.
	always @(negedge TurboEnableClk or negedge nReset)
	begin
		if(!nReset)
			TurboEnabled <= 1'b0;
		else
			TurboEnabled <= TurboOnOff;
	end
	
	// Enable turbo when OUT $FE executed.
	// Disable turbo when OUT $FD (display generation) or OUT $FF (tape save) or halted or reset
	assign TurboOn		= !nIOWR & (AddrL[7:0]==8'hFE);
	assign TurboOff	= (!nIOWR & (AddrL[7:0]==8'hFD))  | !nReset | !nHALT;
	
	always @(posedge TurboOn or posedge TurboOff)
	begin
		if(TurboOff)
			TurboOnOff <= 1'b0;
		else
			TurboOnOff <= 1'b1;
	end
	
	assign PortB[0]	= TurboOn;
	assign PortB[1]	= TurboOff;
	assign PortB[2]	= TurboOnOff;
	assign PortB[3]	= TurboEnabled;
	assign PortB[4]	= Z80CLK;
	assign PortB[5]	= TurboFlag;
	assign PortB[6]	= TurboEnableClk;
		
	// HSync generation
	// Counter will be reset to 0 if the count reaches 207 or vsync is active.
	assign HClear 		= ((HCount==207) & CLK325) | VSync;
	
	always @(negedge CLK325 or posedge HClear)
	begin
		if(HClear)
			HCount <= 0;
		else
			HCount <= HCount+1;
	end
	
	// HSync generated at end of count
	assign HSync	= HCount[6] & HCount[7];
	assign nHSync	= !HSync;
	
	// Generate blanking and border signals
	assign BackPorch	= (HCount < 16);
	assign Blank		= BackPorch | HSync | VSync;
	assign HBorder		= ((HCount < 36) | (HCount > 163));
	assign VBorder		= ((VCount < 56) | (VCount > 247));

	// Chip selects
	// ROMCS active when mREQ valid and A14 low, RAMCS valid when MREQ active and A14 high
	// This effectivly devides the memory map up as follows :
	//	$0000 - $3FFF	ROMCS
	// $4000 - $7FFF	RAMCS
	// $8000 - $BFFF	ROMCS
	// $C000 - $FFFF	RAMCS
	assign nROMCS	= nMREQ | AddrH[14];
	assign nRAMCS	= nMREQ | !AddrH[14];
		
	// VSync generation
	// Read of location $FE
	assign nFERD		= nIORD | AddrL[0];
	assign VSyncOn		= (nFERD | NMIon);
	assign VSyncOff	= nIOWR;
	
	// Togge VSync on and off as needded
 	always @(negedge VSyncOn or negedge VSyncOff)
	begin
		if (!VSyncOn)
			VSync <= 1'b1;
		else
			VSync <= 1'b0;
	end
	
	// NMI control
	assign NMIEnable	= nIOWR | AddrL[0];
	assign NMIDisable	= nIOWR | AddrL[1];
	
	// Toggle NMIon on and off as needed
	always @(negedge NMIEnable or negedge NMIDisable)
	begin
		if (!NMIEnable)
			NMIon <= 1'b1;
		else
			NMIon <= 1'b0;
	end
	
	assign nNMI	= !NMIon | nHSync;
	//assign nNMI = 1'b1;
	
	// Micro counter
	reg MicroEnabled;
	assign L_PRE	= (Micro[3:0]==8); //!Micro[3]; 
	
	always @(posedge L_PRE or negedge nM1)
	begin
		if(L_PRE)
			MicroEnabled <= 1'b0;
		else
			MicroEnabled <= 1'b1;
	end
	
	// Only clock if enabled
	always @(negedge CLK65 or negedge MicroEnabled)
	begin
		if (!MicroEnabled)
			Micro <= 4'b0;
		else 
			Micro <= Micro+1;
	end
	
	// NOP Pulse
	reg	NOPTC;
	
	// On the CPLD the NOP pulse had to stretch over half cycles 3 and 4
	// or the Z80 will sometimes latch the character data instead of the NOP !
	assign NOPulse		= ((Micro[2:0]>=3) & (Micro[2:0]<=4)); 	
	assign NOPEnable	= !(!Data[6] & AddrH[15] & nHALT) | nM1;
	
	always @(posedge NOPulse or posedge L_PRE)
	begin
		if(L_PRE)
			NOPTC <= 1'b1;
		else 
			NOPTC <= NOPEnable;
	end
	
	assign nDO_NOP	= !NOPulse | NOPTC;
	
	// Character latch, latch the character in during half cycle 2, befor the NOP
	// is forced. The bottom 6 bits are the character data, bit 6 is always 0 so is
	// not latched, bit 7 is the Inverse bit
	reg [5:0] CharLatch;
	reg Invert;
	assign CharLatchEnable	= (Micro[2:0] == 3);
	
	always @(posedge CharLatchEnable)
	begin
		CharLatch[5:0] <= Data[5:0];
		Invert <= !Data[7];
	end
	
	always @(negedge HSync or posedge VSync)
	begin
		if(VSync)
			VCount[8:0] <= 9'b0;
		else
			VCount <= VCount+1;
	end
	
	// Output the address of the required bit pattern for half cycles 5 till 7
	// Character row count is 0..7, and taken from the bottom 3 bits of the vertical count
	assign CharLatchOE 	= ((Micro[0] | Micro[1]) & Micro[2]);	// Micro > 4
	assign AddrL[8:0]		= CharLatchOE ? {CharLatch[5:0], VCount[2:0]} : 9'bz; 

	// Data transfer from ULA to Z80 will happen when :
	// The Z80 reads port $FE : keyboard / usa-uk / tape port 
	// The ULA forces a NOP as part of the video cycle.
	// Otherwise the output data lines are tri-stated.
	wire [7:0] DataOut;

	assign DataEn			= !nDO_NOP | !nFERD;
	assign DataOut[7:0]	= nFERD ? {8'b0} : {TapeIn, USAUK, 1'b0, Kbd[4:0]};
	assign Data				= DataEn ? DataOut : 8'bz;
	
	// Load or shift, selects when the video shift register should load data from the 
	// bus or should shift it out as video.
	assign LD_SHIFT		= !(Micro[2:0]==7) | NOPTC;
	
	// Set Fill colour colour
	assign FillColour		= (HBorder | VBorder) ? BorderColour : PaperColour;
	
	// Video shifter, latches bit pattern data from the bus and shifts out the video.
	// If the invert bit is set then the data is inverted as it is latched. 
	always @(posedge CLK65 /*or negedge LD_SHIFT*/)
	begin
		if (!LD_SHIFT)
			ShiftReg[7:0] <= (Invert ^ !PaperColour) ? ~Data[7:0] : Data[7:0];
		else 
			ShiftReg[7:0] <= {ShiftReg[6:0], FillColour};
	end
	
	// Generate video from blanking and syncs
	assign VidOut	= Blank ? 1'b0 : ShiftReg[7];
	assign CSync	= !HSync ^ VSync;
	
	assign KeyReadStrobe	= ((AddrL[7:0]==8'hFE) & !nIORD & !AddrH[15]);
	
	always @(posedge KeyReadStrobe or negedge nReset)
	begin
		if(!nReset)
		begin
			Keys[4:0] <= 5'b0;
			KeysLatched <= 1'b0;
		end
		else
		begin
			if(!KeysLatched)
			begin
				Keys[4:0] <= ~Kbd[4:0];
				KeysLatched <= 1'b1;
			end
		end
	end
	
	// For debug.
	//assign PortA[7:0]	= {3'b0,Micro[3:0],CLK65};
	//assign PortB[7:0]	= {HSync,VSync,DataEn,CharLatchOE, Micro[3:0]};
	//assign PortC[7:0]	= {NMIEnable,NMIDisable,nDO_NOP , NOPTC,NOPEnable,NOPulse , NMIon, nNMI};
	
endmodule
If anyone can spot anything glaringly wrong with the clock change code feel free to point it out to me.

Cheers.

Phill.
User avatar
Andy Rea
Posts: 1606
Joined: Fri May 09, 2008 2:48 pm
Location: Planet Earth
Contact:

Re: ULA revistited.

Post by Andy Rea »

Using this snippet...

Code: Select all

module cdub (
         
CLKIN,
CLKOUT,
TurboOn,
TurboOff,
TurboFlag,
nReset
);

input CLKIN;
output CLKOUT;
input TurboOn;
input TurboOff;
input nReset;
input TurboFlag;

reg CLK325;
reg TurboEnabled;
reg TurboOnOff;

// Clock generator output clock to Z80 (3.25MHz), is generated by dividing input clock
   // 6.5MHz by 2.
   always @(negedge CLKIN)
   begin
      CLK325 <= !CLK325;
   end
   
   // Aliases just for convenience
   assign CLK65   = CLKIN;
   assign nCLK65   = !CLKIN;
   
   // Assign Clock to Z80 depending on current turbo setting.
   assign Z80CLK    = TurboEnabled ? CLK65 : CLK325;
   
   assign CLKOUT   = Z80CLK;
   
   // We should only change TurboEnabled when *BOTH* clocks are low
   assign TurboEnableClk   = (!CLK65 & !CLK325 & TurboFlag & nReset);
   
   // Latch current turbo setting syncronously with clocks being low, if
   // reset is low, then set Turbo off.
   always @(negedge TurboEnableClk or negedge nReset)
   begin
      if(!nReset)
         TurboEnabled <= 1'b0;
      else
         TurboEnabled <= TurboOnOff;
   end
   
   // Enable turbo when OUT $FE executed.
   // Disable turbo when OUT $FD (display generation) or OUT $FF (tape save) or halted or reset
   
 //  assign TurboOn      = !nIOWR & (AddrL[7:0]==8'hFE);
//   assign TurboOff   = (!nIOWR & (AddrL[7:0]==8'hFD))  | !nReset | !nHALT;
   
   always @(posedge TurboOn or posedge TurboOff)
   begin
      if(TurboOff)
         TurboOnOff <= 1'b0;
      else
         TurboOnOff <= 1'b1;
   end
   
endmodule
It appears that when switching from turbo back to non-turbo the CLKOUT glitches.
glitch.PNG
glitch.PNG (7.86 KiB) Viewed 8729 times
what's that Smell.... smells like fresh flux and solder fumes...
User avatar
Andy Rea
Posts: 1606
Joined: Fri May 09, 2008 2:48 pm
Location: Planet Earth
Contact:

Re: ULA revistited.

Post by Andy Rea »

This...

Code: Select all

module cdub (
         
clkin,
clkout,
turboon,
turbooff,

);

input clkin;
output clkout;
input turboon;
input turbooff;


reg turbo;
reg clk325;

// Clock generator output clock to Z80 (3.25MHz), is generated by dividing input clock
   // 6.5MHz by 2.
   always @(negedge clkin)
   begin
      clk325 <= !clk325;
   end

always @ (negedge clkin) begin
	if (turboon & clk325) turbo <= 1'b1 ;
	else if (turbooff & clk325) turbo <= 1'b0;
	else turbo <= turbo ;
end

assign clkout = turbo ? clkin : clk325 ;
	



  
   
endmodule
Seems to work quite nicely...

turboon, turbooff are both active high, and need to be at least as long to cover 2 negedeg transisitions of the clkin, to catch the right phase of the clk325, but they can be as long as they like.

Regards Andy
what's that Smell.... smells like fresh flux and solder fumes...
User avatar
Andy Rea
Posts: 1606
Joined: Fri May 09, 2008 2:48 pm
Location: Planet Earth
Contact:

Re: ULA revistited.

Post by Andy Rea »

or simpler still...

Code: Select all

//always @ (negedge clkin) begin

always @ (negedge clk325) begin

	//if (turboon & clk325) turbo <= 1'b1 ;
	//else if (turbooff & clk325) turbo <= 1'b0;

	if (turboon) turbo <= 1'b1 ;
	else if (turbooff) turbo <= 1'b0;
	
	else turbo <= turbo ;
end
since as the always block is triggered on the negedge of clk325 we know that clk65 has already gone low (a few ns previously) and in either case turbo / non turbo clkout will also be low too.

of course you need to wrap it up in the rest of the switching code :D


Regards Andy
what's that Smell.... smells like fresh flux and solder fumes...
Post Reply