Can you (please) attach some pictures?Prime wrote: YepPretty much designed one night, exposed-developed-etched then drilled and soldered th next day
![]()
ULA revistited.
Re: ULA revistited.
In theory, there is no difference between theory and practice. But, in practice, there is.
Re: ULA revistited.
I tried but they where too bigPaul wrote:Can you (please) attach some pictures?Prime wrote: YepPretty much designed one night, exposed-developed-etched then drilled and soldered th next day
![]()

Phill.
Re: ULA revistited.
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
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

In theory, there is no difference between theory and practice. But, in practice, there is.
Re: ULA revistited.
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....Paul wrote:Thanks, it looks great.
This starts another lot of questions.
Will this be able to fit into the original Case?
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.Would there be space for some extensions like ps/2 Keyboard-interface?
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.A Monitor output would be easy I think.
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.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
Cheers.
Phill.
Re: ULA revistited.
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.
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...
Re: ULA revistited.
I built the keyboard portion of this and it worked well.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 ?
http://www.user.dccnet.com/wrigter/inde ... XKBDv3.htm
Re: ULA revistited.
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 :
If anyone can spot anything glaringly wrong with the clock change code feel free to point it out to me.
Cheers.
Phill.
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
Cheers.
Phill.
Re: ULA revistited.
Using this snippet...
It appears that when switching from turbo back to non-turbo the CLKOUT glitches.
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
what's that Smell.... smells like fresh flux and solder fumes...
Re: ULA revistited.
This...
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
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
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...
Re: ULA revistited.
or simpler still...
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
Regards Andy
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
of course you need to wrap it up in the rest of the switching code

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