Files
C16_MiSTer/rtl/ted.v
PhantombrainM 47d574990a Update ted.v (#10)
Fixes the Genius Epic Demo end scene
2022-07-14 03:24:23 +08:00

1722 lines
58 KiB
Verilog

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Copyright 2013-2016 Istvan Hegedus
//
// FPGATED is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// FPGATED is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// Create Date: 12/18/2013 - 31/03/2016
// Design Name: MOS 8360 video chip
// Module Name: ted.v
// Project Name: FPGATED
// Description: Cycle exact MOS 8360 TED display chip
//
// Revision history:
// 0.2 12/11/2015 diag 264 runs, all screenmodes implemented, external dram works, scroll bug in diag264
// 0.3 22/01/2016 DRAM resfresh horizontal events improved (increment start/stop, counter reset),vertical scroll bug in Invincible, FF1E write bug in New FLI, FLI incorrect
// 0.4 03/02/2016 VertSub counter fixed for Invincible start screen
// 0.5 22/02/2016 Raster interrupt fixed, Invincible does not freeze now
// 0.6 03/03/2016 Multicolor Character mode bug fixed in pixelgenerator. Majesty Of Sprites looks good now
// 0.7 30/03/2016 Audio sound generator and audio D/A implemented
// 1.0 14/07/2016 First public release, functionally equivalent to 0.7, code cleaned up, license information added
//////////////////////////////////////////////////////////////////////////////////
module ted
(
input clk, // clk must be 4*dot clk so 28.375152MHz for PAL (1.6*PAL system's clock) and 28.63636 for NTSC (2*NTSC system's clock)
input reset,
input [15:0] addr_in,
output [15:0] addr_out,
input [7:0] data_in,
output [7:0] data_out,
input rw,
output cpuclk, // this is a CPU clock for external real CPU
output [6:0] color, // 7 bits color code
output csync,
output reg hsync,
output reg vsync,
output reg hblank,
output reg vblank_out,
input wide,
output ce_pix,
output irq,
output ba,
output reg cs_io,
output reg cs_ram,
output reg cs0,
output reg cs1,
output reg aec,
output [4:0] snd,
output pal,
input [7:0] k,
input [1:0] tvmode,
output cpuenable // this TED signals is needed only for FPGA bustiming and FPGA internal cpu. If external CPU is used, it is not needed.
);
// TED register addresses
parameter TIMER1LO=6'h00;
parameter TIMER1HI=6'h01;
parameter TIMER2LO=6'h02;
parameter TIMER2HI=6'h03;
parameter TIMER3LO=6'h04;
parameter TIMER3HI=6'h05;
parameter CONTROL1=6'h06;
parameter CONTROL2=6'h07;
parameter KEYLATCH=6'h08;
parameter IRQ =6'h09;
parameter IRQEN =6'h0A;
parameter RASTER =6'h0B;
parameter CURPOSHI=6'h0C;
parameter CURPOSLO=6'h0D;
parameter CH1FREQLO=6'h0E;
parameter CH2FREQLO=6'h0F;
parameter CH2FREQHI=6'h10;
parameter SOUNDCTRL=6'h11;
parameter BMAPBASE=6'h12;
parameter CHARBASE=6'h13;
parameter VIDEOBASE=6'h14;
parameter BGCOLOR0=6'h15;
parameter BGCOLOR1=6'h16;
parameter BGCOLOR2=6'h17;
parameter BGCOLOR3=6'h18;
parameter EXCOLOR=6'h19;
parameter CHARPOSRELOADHI=6'h1A;
parameter CHARPOSRELOADLO=6'h1B;
parameter VSCANPOSHI=6'h1C;
parameter VSCANPOSLO=6'h1D;
parameter HSCANPOS=6'h1E;
parameter FLASH_VERTSUB=6'h1F;
parameter ROMEN=6'h3E;
parameter RAMEN=6'h3F;
// DMA FSM states
localparam IDLE=3'b000,THALT1=3'b001,THALT2=3'b010,THALT3=3'b011,TDMA=3'b100;
reg [2:0] dma_state=IDLE,dma_nextstate; // DMA FSM state register
// TED user accessible registers
// These registers are the actual TED registers accessible by end users
reg [15:0] timer1=16'b0,timer1_reload=16'b0; // $FF00/01
reg [15:0] timer2=16'b0; // $FF02/03
reg [15:0] timer3=16'b0; // $FF04/05
reg test=1'b0,ecm=1'b0,bmm=1'b0,den=1'b0,rsel=1'b0; // $FF06 control1 register bits
reg [2:0] yscroll=3'b0; // $FF06 bits 0-2, vertical scroll register
reg bmm_reg=1'b0,ecm_reg=1'b0; // delayed registered values of BMM and ECM
reg reverse=1'b0,stop=1'b0,mcm=1'b0,csel=1'b0; // $FF07 control2 register bits
reg [2:0] xscroll=3'b0; // $FF07 bits 0-2, horizontal scroll regitser
reg reverse_reg=1'b0,mcm_reg=1'b0; // delayed registered values of REVERSE and ECM
reg [7:0] keylatch=8'hff; // $FF08 keyboard latch
reg Cnt1Irq=1'b0,Cnt2Irq=1'b0,Cnt3Irq=1'b0,RasterIrq=1'b0,LPIrq=1'b1; // $FF09 IRQ register
reg enCnt1Irq=1'b0,enCnt2Irq=1'b0,enCnt3Irq=1'b0,enRasterIrq=1'b0,enLPIrq=1'b0; // $FF0A IRQ enable register
reg [8:0] RasterCmp=9'b0; // $FF0B
reg [9:0] CursorPos=10'b0; // $FF0C/0D
reg [9:0] Ch1Freq=10'b0; // $FF0E, $FF12 bits 0-1
reg [9:0] Ch2Freq=10'b0; // $FF0F, $FF10 bits 0-1
reg damode=1'b0,ch2noise=1'b0,ch2en=1'b0,ch1en=1'b0; // $FF11 bits 4-7
reg [3:0] volume=4'b0; // $FF11 bits 0-3
reg [2:0] bmapbase=3'b0; // $FF12 bits 3-5, Bitmap base address
reg charrom=1'b0; // $FF12 bit 2
reg [5:0] charbase=6'b0; // $FF13 bits 2-7, Character memory base address
reg clkmode=1'b0; // $FF13 bit 1, force single clock mode
reg [4:0] vmbase=5'b0; // $FF14 bits 3-7, Video RAM base address register
reg [6:0] bgcolor0=7'b0,bgcolor1=7'b0,bgcolor2=7'b0,bgcolor3=7'b0,excolor=7'b0; // $FF15-19 color registers
reg [9:0] CharPosReload=10'b0; // $FF1A/B, Character Position Reload increments by 40 for each character row completed
reg [8:0] vcounter=9'b0; // $FF1C/D, Vertical line counter
reg [8:0] hcounter=9'b0; // $FF1E, Horizontal dot counter. In real TED it is 11bit. Counts from 0 to 455
reg [4:0] FlashCount=5'b0; // $FF1F bits 3-6, Flash counter's 5th bit is the actual flash state and is not user accessible
reg [2:0] VertSubCount=3'b0; // $FF1f bits 0-2, Vertical Character scan line position
// TED internal operational registers
// These are needed for the internal operation
reg [7:0] refreshcounter=8'h00;
reg refresh=1'b0;
reg [3:0] phicounter=4'b0; // CPU single clock generator counter
reg phi=1'b0; // CPU single clock
reg singleclock=1'b0; // signals single clock mode
reg [8:0] hcounter_next;
reg [8:0] vcounter_next;
reg [8:0] videoline=9'b0; // vcounter latched at start of each scanline
reg [7:0] dataout_reg=8'hff; // TED's databus out register
reg [7:0] data_in_reg; // TED databus in register
reg [5:0] addr_in_reg; // TED address in register
reg [6:0] colorreg=7'b0; // video out register
reg ramen=1'b0; // High memory address RAM enable register (above $8000)
reg t1stop=1'b0,t2stop=1'b0,t3stop=1'b0; // Timer disable signals
reg resetRasterIrq,/*resetLpIrq,*/resetCnt1Irq,resetCnt2Irq,resetCnt3Irq; // Interrupt reset signals
reg RasterIrqDone=1'b0; // Signals that raster interrupt has already happened in this line
reg enabledisplay=1'b0; // DEN register changes enabledisplay signal on first scanline only
reg badline2=1'b0; // signals 2nd badline (1st badline signal is a wire)
reg ext_fetch=1'b0; // signals external fetch window inside scanline
reg char_fetch=1'b0; // signals character fetch window inside scanline
reg dma_window=1'b0; // signals active dma range inside a scanline
reg char_window=1'b0; // signals when character/pixel data can be latched from data bus inside a scanline
reg inc_flashcount_window=1'b0; // signals flash counter increase window
reg inc_vertsub_window=1'b0; // active for one single clock cycle, signals vertsub register incrementation point (thus actual increment point is delayed with a single clock cycle)
reg inc_vertline_window=1'b0; // active for one single clock cycle, signals vertical line incrementation point (thus actual increment point is delayed with a single clock cycle)
// horizontal event positions used for the horizontal event decoder. They don't necessarily reflect the values seen in documentation
reg hpos_0,hpos_8,hpos_154,hpos_172,hpos_288,hpos_295,hpos_296,hpos_303,hpos_304;
reg hpos_312,hpos_320,hpos_336,hpos_343,hpos_348,hpos_352,hpos_359,hpos_380,hpos_382;
reg hpos_384,hpos_391,hpos_392,hpos_400,hpos_407,hpos_424,hpos_431,hpos_432,hpos_440,hpos_1,hpos_321;
reg inc_charpos=1'b0; // signals internal character position register (not user accessible) increment range inside scanline (not same as $FF1A/$FF1B)
reg [15:0] addr_out_reg; // TED's address out register
reg [15:0] tedaddress; // this is a non registered TED address (although register variable but used in combinational logic)
reg datahold=1'b0; // signals whether TED should hold its data on the databus
reg VertSubActive=1'b0; // signals the scanline ranges when Vertsub counter is active
reg tedwrite_delay=1'b0; // this signal was needed to emulate a one dot clock delay when TED writes data to its internal registers. Although most probably this delay exist at
// all TED register writes, in FPGATED we use it only for hcounter/vcounter and color register writes. This emulates white pixel bug too.
reg csyncreg=1'b0,palreg=1'b0,equalization=1'b0,eq1=1'b1,eq2=1'b1; // PAL/NTSC video screen signals
reg [9:0] videocounter=10'b0; // videocounter is the actual DMA counter.
reg inc_videocounter=1'b0; // signals videocounter increment window
reg [9:0] videocounter_reload=10'b0; // videocounter is reloaded with this value at the beginning of each displayed line
reg [9:0] CharPosition=10'b0; // CharPosition is loaded by $FF1A/$FF1B and is similar to videocounter. It is used for pixel data fetch from memory.
reg CharPosLatch=1'b0; // Signals latching position of CharPosition and videocounter
reg latch_window=1'b0; // CharPosition and videocounter latch delay window
reg latch_charposition=1'b0; // Charposition and videocounter latch position
reg [7:0] attr_buf [0:39]; // TED internal videomatrix attribute memory
reg [7:0] char_buf [0:39]; // TED internal videomatrix character pointer memory
reg [7:0] nextchar=8'b0,currentchar=8'b0,waitingchar=8'b0,pixelchar=8'b0; // next...,current... is a 2 bytes shiftregister to keep data until rendering. waiting... is waiting to be loaded to rendering shiftregister
reg [7:0] nextattr=8'b0,currentattr=8'b0,waitingattr=8'b0,pixelattr=8'b0;
reg [7:0] nextpixels=8'b0,currentpixels=8'b0,waitingpixels=8'b0;
reg [7:0] pixelshiftreg=8'b0; // This register contains pixel data and shifts it during rendering
reg [5:0] shiftcount=6'b0; // Used by the videomatrix shift register to count number of shifts
reg verticalscreen=1'b0; // Signals which lines are in screen area (top/bottom border control)
reg widescreen=1'b0,narrowscreen=1'b0; // Signals horizontal screen area (left/right border control)
reg videoshift=1'b0; // Signals when vide shoft register is active
reg nextcursor=1'b0,currentcursor=1'b0,waitingcursor=1'b0; // cursor state internal storage for 3 signle clock cycles
reg [6:0] pixelcolor; // Color of a pixel
reg doubleshift=1'b0; // During multicolor mode 2 pixels identify one pixel, so this register signals to pixel generator wheter to shift one or two pixels to get color data
reg dotfetch; // Signals when TED is fetching pixeldata from databus
reg dotfetch_reg=1'b0; // Registered version of dotfetch
reg [2:0] xscroll_latch=3'b0; // Registered version of xscroll register
reg [2:0] yscroll_latch=3'b0; // Registered version of yscroll register
reg vblank=1'b0; // Signals blanking area
reg refresh_inc=1'b0; // Dram refresh counter increment window
reg stopreg=1'b0; // This is a latched version of stop register. Latched at single cycle end
// audio part registers
reg [1:0] audiocycle=2'b0; // Audio cycle counter divides single clock by 4 and generates audio clock
reg [9:0] ch1count=10'b0,ch2count=10'b0; // Audio channel1 and channel2 counters
reg ch1state=1'b0,ch2state=1'b0; // State register of audio channels
reg ch1stateclk_prev=1'b0,ch2stateclk_prev=1'b0;
reg [7:0] noisegen=8'b0; // Noise generator register
reg [4:0] digivolume=5'b0; // A digital value signaling at which pwmclock cycle PWM signal high value starts. A digitized version of volume level
reg [17:0] watchdog_ch1=18'b0,watchdog_ch2=18'b0; // Watchdog timer to emulate sound decay of TED's dynamic latch behaviour
integer i;
integer j;
integer n;
// Internal wires, flags
wire dphi; // double phi clock
wire [8:0] EOS,VS_START,VS_STOP,EQ_START,EQ_STOP,VBLANK_START,VBLANK_STOP; //video signal generation constants. Vertical Sync, Equalization, Blank
wire tick8; // enable tick for pixelclock (8MHz)
wire blanking; // screen blanking area flag
wire lowrom,highrom; // TED low and high rom area flags
wire irqpos; // IRQ position flag inside clock cycle. Emulates real TED's IRQ signal activation position
wire io,tedreg,tedwrite; // IO area flag, TED user registers area flag, TED write cycle flag (signals when TED register is written by CPU)
wire badline; // badline flag
wire attr_fetch_line; // Visible screen area flag (signals active window)
wire tedlatch; // tedlatch simulates at which exact position TED latches value into its internal register from the databus
wire [7:0] charpointer,attrpointer;
wire multicolor; // multicolor mode flag
wire pixelscreen; // visible pixelscreen area flag (excluding borders)
wire ch1clk,ch2clk; // Audio channel clocks
wire ch1stateclk,ch2stateclk; // Audio state register change clock
wire ch1audio,ch2audio; // Audio channel square waves not modulated by volume (before PWM)
wire noise; // Noise
wire watchdog_ch1max,watchdog_ch2max; // Audio watchdog timer maximum values. Actual value is taken from plus4emu
// Initializing internal video matrix
initial
begin
for(i=0;i<=39;i=i+1)
begin
char_buf[i]=0;
attr_buf[i]=0;
end
end
//-----------------------------------------------------------------------
// Often used combinational signals
//-----------------------------------------------------------------------
wire cycle_end=(phicounter==15)?1'b1:1'b0; // high pulse at the end of each double clock cycle
wire single_cycle_end=(cycle_end & phi)?1'b1:1'b0; // high pulse at the end of each single clock cycle
//-----------------------------------------------------------------------
// Clock signal driver phi=Single Clock dphi=Double Clock
//-----------------------------------------------------------------------
always @(posedge clk) begin // Counting FPGA clock cycles during double clock. phicounter is mod16 counter, 16*clk=half phi
phicounter<=phicounter+1'd1;
end
assign cpuclk = singleclock?phi:dphi; // Generated CPU clock. Used only when real 8501 CPU is connected to FPGA
assign dphi = phicounter[3]; // Internal double clock signal
assign cpuenable=(single_cycle_end)?1'b1: // Generated CPU enable signal. Used only when FPGA CPU is used
(cycle_end && !singleclock)?1'b1:
1'b0;
always @(posedge clk) begin // Internal single clock signal is always generated
if (cycle_end) phi<=~phi;
end
always @(posedge clk) // clock mode controller. Single or double clock multiplex for the CPU.
begin
if(single_cycle_end) // clock mode change happens only at single clock boundary
singleclock<=((enabledisplay & ext_fetch) | refresh | clkmode | stop); // there are 4 criterias to generate single clock: display area,dram refresh,forced 1Mhz,TED stop
end
always @(posedge clk)
begin
if(single_cycle_end)
stopreg<=stop;
end
//--------------------------------------------------------------------------
// Attribute Fetch
//--------------------------------------------------------------------------
always @(posedge clk) // flip flop to signal external fetch single clock window, delayed with 1 single clock cycle
begin
if(hpos_296)
ext_fetch<=0;
else if(hpos_400)
ext_fetch<=1;
end
assign attr_fetch_line=(videoline>=0 && videoline<203);
//--------------------------------------------------------------------------
// DRAM Refresh
//--------------------------------------------------------------------------
always @(posedge clk) // refresh single clock control
begin
if(hpos_336)
refresh<=0;
else if(hpos_296)
refresh<=1;
end
always @(posedge clk) // refresh counter increment control
begin
if(hpos_343)
refresh_inc<=0;
else if(hpos_303)
refresh_inc<=1;
end
always @(posedge clk)
begin
if(single_cycle_end & (refresh_inc|stopreg))
refreshcounter<=refreshcounter+1'd1;
else if(hpos_431 & (videoline==0|refresh_inc|stopreg))
refreshcounter<=8'h00;
end
//-------------------------------------------------------------------------------------------
// Horizontal counter running on ~8Mhz and vertical counter qualified by horizontal counter
//-------------------------------------------------------------------------------------------
assign tick8=(phicounter[1:0]==3)?1'b1:1'b0; //8Mhz clock tick for pixelclock. tick8 must activate one fastclk cycle earlier to use it for hcounter
assign ce_pix = tick8;
always @(posedge clk)
begin
hcounter<=hcounter_next;
vcounter<=vcounter_next;
if(hpos_392)
videoline<=vcounter;
end
always @* //horizontal counter next state logic
begin
hcounter_next=hcounter;
if (tedlatch & addr_in_reg[5:0]==HSCANPOS) // horizontal counter is written by CPU
begin
hcounter_next=hcounter+1'd1;
hcounter_next[8:3]=~data_in_reg[7:2]; // bit 0-2 are not modified by user write to prevent clock phase change
end
else if (tick8 & ~stopreg)
begin
if (hcounter==9'd455)
hcounter_next=9'd0;
else
hcounter_next=hcounter+1'd1;
end
end
always @* //vertical counter next state logic
begin
vcounter_next=vcounter;
if(tedwrite & addr_in[5:1]==5'b01110) // $ff1c or $ff1d register write (VSCAN HI and LO)
begin
if(addr_in[0]==0)
vcounter_next={data_in[0],vcounter[7:0]};
else vcounter_next={vcounter[8],data_in};
end
else if(inc_vertline_window & single_cycle_end)
begin
if (vcounter==EOS)
vcounter_next=0;
else vcounter_next=vcounter+1'd1;
end
end
always @(posedge clk)
begin
if(hpos_384)
inc_vertline_window<=1;
else if (single_cycle_end)
inc_vertline_window<=0;
end
//---------------------------------------------------------------------------
// Timer 1
//---------------------------------------------------------------------------
// timer 1 decrements during odd single clock cycle (phi=0)
// exact counter change position is unknown but can be estimated based on IRQ place and reading counter values every cycle on a real hardware
// timer 1 changes approximately at half of phi low cycle after IRQ position (IRQ position is 160ns after phi low cycle start).
//
always @(posedge clk) begin
if(tedwrite) // load timer 1 at cycle border
begin
if (addr_in[5:0]==TIMER1LO)
begin
timer1[7:0]<=data_in;
timer1_reload[7:0]<=data_in;
t1stop<=1;
end
if (addr_in[5:0]==TIMER1HI)
begin
timer1[15:8]<=data_in;
timer1_reload[15:8]<=data_in;
t1stop<=0;
end
end
if(phicounter==7 && ~phi & ~t1stop & ~stopreg) // decrement or reload timer 1
begin
if(timer1==0)
timer1<=timer1_reload-1'd1;
else
timer1<=timer1-1'd1;
end
if(reset) begin
t1stop <= 1;
timer1 <= 16'hFFFF;
end
end
//---------------------------------------------------------------------------
// Timer 2
//---------------------------------------------------------------------------
// timer 2 decrements during even single clock cycle (phi=1)
// timer 2 changes approximately at odd-even single clock cycle boundary (phi low - high transition)
always @(posedge clk) begin
if(tedwrite) // load timer 2 at cycle border
begin
if (addr_in[5:0]==TIMER2LO)
begin
timer2[7:0]<=data_in;
t2stop<=1;
end
if (addr_in[5:0]==TIMER2HI)
begin
timer2[15:8]<=data_in;
t2stop<=0;
end
end
else if(phicounter==15 && phi==0 && t2stop==0 && stopreg==0) // if not loaded, decrement timer 2 at odd-even cycle border
begin
timer2<=timer2-1'd1;
end
if(reset) begin
t2stop <= 1;
timer2 <= 16'hFFFF;
end
end
//---------------------------------------------------------------------------
// Timer 3
//---------------------------------------------------------------------------
// timer 3 decrements during even single clock cycle (phi=1)
// timer 3 changes approximately at half of phi high cycle (contrary to timer 1)
always @(posedge clk) begin
if(tedwrite)
begin
if (addr_in[5:0]==TIMER3LO) // load timer 3 at cycle border
begin
timer3[7:0]<=data_in;
t3stop<=1;
end
if (addr_in[5:0]==TIMER3HI)
begin
timer3[15:8]<=data_in;
t3stop<=0;
end
end
if(phicounter==7 && phi==1 && t3stop==0 && stopreg==0) // decrement timer 3
begin
timer3<=timer3-1'd1;
end
if(reset) begin
t3stop <= 1;
timer3 <= 16'hFFFF;
end
end
//---------------------------------------------------------------------------
// Timer IRQs
//---------------------------------------------------------------------------
//
assign irqpos=(phicounter==4 & ~phi)?1'b1:1'b0;
always @(posedge clk)
begin
if(reset|resetCnt1Irq)
Cnt1Irq<=0;
else if(irqpos && timer1==0)
Cnt1Irq<=1;
end
always @(posedge clk)
begin
if(reset|resetCnt2Irq)
Cnt2Irq<=0;
else if(irqpos && timer2==0)
Cnt2Irq<=1;
end
always @(posedge clk)
begin
if(reset|resetCnt3Irq)
Cnt3Irq<=0;
else if(irqpos && timer3==0)
Cnt3Irq<=1;
end
//---------------------------------------------------------------------------
// Raster IRQ
//---------------------------------------------------------------------------
always @(posedge clk)
begin
if (reset|resetRasterIrq)
RasterIrq<=0;
else if (RasterCmp==vcounter)
begin
if(~RasterIrqDone & tick8) // do raster interrupt only 1 time per raster line and interrupt happens when phi or dphi is low and about 170ns after cycle start
begin
RasterIrq<=1;
RasterIrqDone<=1;
end
end
else RasterIrqDone<=0;
end
//---------------------------------------------------------------------------
// IRQ signal
//---------------------------------------------------------------------------
assign irq=~((enCnt1Irq & Cnt1Irq)|(enCnt2Irq & Cnt2Irq)| (enCnt3Irq & Cnt3Irq) | (enRasterIrq & RasterIrq) | (enLPIrq & LPIrq));
//---------------------------------------------------------------------------
// AEC signal generating
//---------------------------------------------------------------------------
always @(posedge clk)
begin
if((singleclock & ~phi) | (dma_state==TDMA))
aec<=0;
else aec<=1;
end
//---------------------------------------------------------------------------
// BA signal (RDY)
//---------------------------------------------------------------------------
assign ba=(dma_state==IDLE)?1'b1:1'b0;
//---------------------------------------------------------------------------
// badline
//---------------------------------------------------------------------------
assign badline=((yscroll_latch==videoline[2:0]) & enabledisplay & attr_fetch_line)?1'b1:1'b0; // signal 1st badline
always @(posedge clk)
begin
if(inc_vertline_window & single_cycle_end)
begin
if(badline)
badline2<=1;
else if(badline2)
badline2<=0;
end
/* else if(badline & ~hpos_392) //when yscroll changed to generate badline, abort an already started badline2 (except at start of line)
badline2<=0;*/
end
always @(posedge clk) // synchronize yscroll changes to single cycle border
begin
if(single_cycle_end)
yscroll_latch<=yscroll;
end
//---------------------------------------------------------------------------
// EnableDisplay signal
//---------------------------------------------------------------------------
always @(posedge clk)
begin
if(videoline==0 && den==1)
enabledisplay<=1;
if(videoline==204)
enabledisplay<=0;
end
//---------------------------------------------------------------------------
// Bitmapmask fetch signal
//---------------------------------------------------------------------------
always @(posedge clk) // character fetch window starts at first badline2 and stops at line 204. It signals that character fetches can happen in these lines.
begin
if(videoline==9'd204)
char_fetch<=0;
else if(badline2)
char_fetch<=1;
end
//----------------------------------------------------------------------------
// Character Position register $FF1A/$FF1B
//----------------------------------------------------------------------------
always @(posedge clk) // character fetch position increase from horizontal count 432 to horizontal count 296
begin
if(hpos_296)
inc_charpos<=0;
else if(hpos_432)
inc_charpos<=1;
end
always @(posedge clk) // DMA and Charpos latch delay trick
begin
latch_charposition<=0;
if(hpos_288)
latch_window<=1;
else if(single_cycle_end & latch_window)
begin
latch_window<=0;
latch_charposition<=1; // 1 FPGA cycle long latch enable signal used for Character position and videocounter position reload latch
end
end
always @(posedge clk)
begin
if(hpos_392)
begin
if(VertSubCount==6)
CharPosLatch<=1; // CharPosLatch signal activates in line 6 and signals that videocounter (DMA counter) has been latched. It is used in line 7 for character position latch.
else
CharPosLatch<=0;
end
end
always @(posedge clk) // Character Position Reload register $FF1A/$FF1B
begin
if(tedwrite & addr_in[5:0]==CHARPOSRELOADHI)
CharPosReload[9:8]<=data_in[1:0];
else if(tedwrite & addr_in[5:0]==CHARPOSRELOADLO)
CharPosReload[7:0]<=data_in;
else if(hpos_392 & videoline==EOS) // clear character position reload at last line
CharPosReload<=0;
else if(CharPosLatch & latch_charposition & VertSubActive) // latch character position at 7th line of a character row if videocunter was latched in previous 6th row
CharPosReload<=CharPosition;
end
always @(posedge clk) // Character Position counter (not user accessible)
begin
if(hpos_392) // clear character position in each line at 392
CharPosition<=0;
else
begin
if(hpos_432 & enabledisplay & VertSubActive) // FIXME this might need delay
CharPosition<=CharPosReload;
else if(inc_charpos & single_cycle_end)
CharPosition<=CharPosition+1'd1;
end
end
//---------------------------------------------------------------------------
// Attribute fetch (DMA)
//---------------------------------------------------------------------------
// DMA FSM
always @(posedge clk)
begin
dma_state<=dma_nextstate;
end
always @*
begin
dma_nextstate=dma_state;
case(dma_state)
IDLE: begin
if((badline|badline2) & dma_window)
dma_nextstate=THALT1;
end
THALT1: begin
if((badline|badline2) & dma_window & single_cycle_end)
dma_nextstate=THALT2;
else if (~dma_window | ~(badline|badline2))
dma_nextstate=IDLE;
end
THALT2: begin
if((badline|badline2) & dma_window & single_cycle_end)
dma_nextstate=THALT3;
else if (~dma_window | ~(badline|badline2))
dma_nextstate=IDLE;
end
THALT3: begin
if((badline|badline2) & dma_window & single_cycle_end)
dma_nextstate=TDMA;
else if (~dma_window | ~(badline|badline2))
dma_nextstate=IDLE;
end
TDMA: begin
if (~dma_window | ~(badline|badline2))
dma_nextstate=IDLE;
end
default: dma_nextstate=IDLE;
endcase
end
always @(posedge clk)
begin
if(hpos_407 & tick8)
dma_window<=1;
else if(hpos_295 & tick8)
dma_window<=0;
end
//-------------
// Attribute fetch address generation (videocounter is DMA position counter)
//-------------
always @(posedge clk) // videocounter increase window
begin
if(enabledisplay)
begin
if(hpos_296 | shiftcount==6'd40)
inc_videocounter<=0;
else if(hpos_432)
inc_videocounter<=1;
end
end
always @(posedge clk)
begin
if(hpos_392 & videoline==EOS) // clear videocounter reload register at last line
videocounter_reload<=0;
else if(inc_videocounter && hcounter_next == 9'd432 && tick8) // if the videocounter running when it's reloaded, that affects the reload value (HSP in Alpharay)
videocounter_reload<=videocounter+1'd1;
else if(VertSubCount==6 && latch_charposition && enabledisplay) // Latch videocounter position at 6th line of a character row
videocounter_reload<=videocounter;
end
always @(posedge clk) // videocounter used for attribute and character pointer fetches (DMA counter)
begin
if(enabledisplay)
begin
if(hpos_432)
videocounter<=videocounter_reload;
else if(inc_videocounter & single_cycle_end) // increase videocounter at cycle border
videocounter<=videocounter+1'd1;
end
end
//------------------------------------
// Internal VideoMatrix (DMA buffers)
//------------------------------------
always @(posedge clk)
begin
if(single_cycle_end)
begin
if(inc_videocounter)
begin
if(badline) begin // in 1st badline fetch attribute from databus and place to buffer's start
attr_buf[0]<=data_in;
end
else begin
attr_buf[0]<=attr_buf[39];
end
for(i=1;i<40;i=i+1) begin
attr_buf[i]<=attr_buf[i-1];
end
nextattr<=attr_buf[39];
shiftcount<=shiftcount+1'd1;
if(((CursorPos==CharPosition) && VertSubActive) || (CursorPos==0 && CharPosition==0)) // cursor position must be checked here
nextcursor<=1;
else nextcursor<=0;
end
else begin
nextattr<=0;
shiftcount<=0;
end
end
end
always @(posedge clk)
begin
if(single_cycle_end)
begin
if(inc_videocounter)
begin
if(badline2) begin
char_buf[0]<=data_in;
nextchar<=data_in;
end
else begin
char_buf[0]<=char_buf[39];
nextchar<=char_buf[39];
end
for(j=1;j<40;j=j+1) begin
char_buf[j]<=char_buf[j-1];
end
end
else begin
nextchar<=0;
end
end
end
always @(posedge clk) // character window flag is needed for fetching pixel data from bus
begin
if(hpos_304)
char_window<=0;
else if(hpos_440 & enabledisplay)
char_window<=1;
end
always @(posedge clk) // latch pixel data from data bus at phi0 change from 0 to 1
begin
if(char_window)
begin
if(hpos_440)
nextpixels<=0;
else if(cycle_end & ~phi)
nextpixels<=data_in;
end
end
//---------------------------------------------------------------------------
// Vertical Sub register represents actual raster line inside character
//---------------------------------------------------------------------------
always @(posedge clk)
begin
if(hpos_392)
inc_vertsub_window<=1;
else if(single_cycle_end)
inc_vertsub_window<=0;
if (hpos_380 & badline) // ... activates at 1st badline of the frame
VertSubActive<=1;
else if (~enabledisplay) // ... inactivates at line 204
VertSubActive<=0;
end
always @(posedge clk)
begin
if(tedwrite && addr_in[5:0]==FLASH_VERTSUB) // if it is written by user
VertSubCount<=data_in[2:0];
else
if(inc_vertsub_window & single_cycle_end) // if it is time to change VertSub
if (videoline==0) // ... changes to 7 at line 0 FIXME: between cycle $C8 and $CA
VertSubCount<=3'd7;
else if(enabledisplay & VertSubActive)
VertSubCount<=VertSubCount+1'd1; // ... increases between line 0 and 204
end
//---------------------------------------------------------------------------
// Flash counter
// 5th bit of FlashCount contains flash status and not accessible via FF1F register
//---------------------------------------------------------------------------
always @(posedge clk)
begin
if(hpos_348)
inc_flashcount_window<=1;
else if(single_cycle_end)
inc_flashcount_window<=0;
if(tedwrite && addr_in[5:0]==FLASH_VERTSUB)
FlashCount[3:0]<=data_in[6:3];
else if(videoline==205 & inc_flashcount_window & single_cycle_end)
FlashCount<=FlashCount+1'd1;
end
//---------------------------------------------------------------------------
// Horizontal event decodes
//---------------------------------------------------------------------------
always @(hcounter)
begin
hpos_0=0;
hpos_1=0;
hpos_8=0;
hpos_154=0;
hpos_172=0;
hpos_288=0;
hpos_295=0;
hpos_296=0;
hpos_303=0;
hpos_304=0;
hpos_312=0;
hpos_320=0;
hpos_321=0;
hpos_336=0;
hpos_343=0;
hpos_348=0;
hpos_352=0;
hpos_359=0;
hpos_380=0;
hpos_382=0;
hpos_384=0;
hpos_391=0;
hpos_392=0;
hpos_400=0;
hpos_407=0;
hpos_424=0;
hpos_431=0;
hpos_432=0;
hpos_440=0;
case (hcounter)
0: hpos_0=1; // Start of 40 column screen
1: hpos_1=1;
8: hpos_8=1; // Start of 38 column screen
154: hpos_154=1; // Equalization pulse 1 start
172: hpos_172=1; // Equalization pulse 1 end
288: hpos_288=1; // CharPosition and Videocounter latch position delayed by 1 cycle (starts at 296)
295: hpos_295=1; // Attribute fetch (DMA) FSM stop
296: hpos_296=1; // Stop external fetch single clock delayed by 1 cycle
// Start refresh singleclock delayed by 1 cycle (actual start at 304)
303: hpos_303=1; // Start refresh counter increment (304 in real TED)
304: hpos_304=1; // End of character window
312: hpos_312=1; // End of 38 column screen
320: hpos_320=1; // End of 40 column screen
321: hpos_321=1;
336: hpos_336=1; // Stop refresh singleclock but delayed by 2 cycle (actual stop at 344)
343: hpos_343=1; // Stop refresh counter increment (344 in real TED)
348: hpos_348=1; // Flash (blink) counter increment point delayed by 2 cycles (increments at 352)
352: hpos_352=1; // Horizontal blanking start
359: hpos_359=1; // Horizontal sync start (358 in real TED however line change takes time thus the delay)
380: hpos_380=1;
382: hpos_382=1; // Equalization pulse 2 start
384: hpos_384=1; // End Of Screen. Clear vertical line,refresh counters and character reload register, increase vertical line after 1 cycle delay
391: hpos_391=1;
392: hpos_392=1; // VertSub register increment (delayed), Hsync end
400: hpos_400=1; // Start external fetch single clock (delayed), Equalization pulse 2 end
407: hpos_407=1; // Attribute fetch (DMA) FSM start
424: hpos_424=1; // Horizontal blanking stop
431: hpos_431=1; // Refresh counter reset point
432: hpos_432=1; // Start videocounter increment
440: hpos_440=1; // Start video shiftregister
default:;
endcase
end
//---------------------------------------------------------------------------
// Border control
//---------------------------------------------------------------------------
always @(posedge clk) // 25/24 row select and top/bottom borders
begin
if(rsel==1) begin
if(videoline==9'd4) // if 25 rows mode, screen starts at line 4
verticalscreen<=1;
else if (videoline==9'd204) // stops at line 204
verticalscreen<=0;
end
else begin
if(videoline==9'd8) // if 24 rows mode, screen starts at line 8
verticalscreen<=1;
else if(videoline==9'd200) // stops at line 200
verticalscreen<=0;
end
end
always @(posedge clk) // 38/40 columns select and side borders
begin
if(enabledisplay & verticalscreen)
begin
if(hpos_320 & tick8)
widescreen<=0;
else if (hpos_0 & tick8)
widescreen<=1;
if(hpos_312 & tick8)
narrowscreen<=0;
else if (hpos_8 & tick8)
narrowscreen<=1;
end
end
//---------------------------------------------------------------------------
// VideoShift Register
//---------------------------------------------------------------------------
always @(posedge clk)
begin
if (hpos_312)
videoshift<=0;
else if(enabledisplay & hpos_440)
videoshift<=1;
end
always @(posedge clk) // video shift register stores fetched video data until pixelshiftregister is loaded
begin
if(hpos_440)
begin
waitingattr<=0;
waitingchar<=0;
waitingpixels<=0;
currentattr<=0;
currentchar<=0;
currentpixels<=0;
end
else if(cycle_end & videoshift)
begin
if(phi)
begin
currentchar<=nextchar;
waitingchar<=currentchar;
currentattr<=nextattr;
waitingattr<=currentattr;
waitingpixels<=currentpixels;
currentcursor<=nextcursor;
waitingcursor<=currentcursor;
end
else if(~phi)
currentpixels<=nextpixels;
end
end
wire cursor=(waitingcursor & ~FlashCount[4]);
//---------------------------------------------------------------------------
// Pixel Generator
// Final screen is delayed by 2 pixels
//---------------------------------------------------------------------------
always @(posedge clk) // synchronize xscroll and display mode changes to single cycle border
begin
if (single_cycle_end)
begin
xscroll_latch<=xscroll;
ecm<=ecm_reg;
bmm<=bmm_reg;
reverse<=reverse_reg;
mcm<=mcm_reg;
end
end
always @(posedge clk) // video pixel shift tregister
begin
if(videoshift | widescreen) // shift register works only when beam is on wide screen area
begin
if(tick8)
begin
doubleshift<=~doubleshift;
if(hcounter[2:0] == xscroll_latch) // load register based on xscroll
begin
doubleshift<=0;
if(cursor & ~bmm & ~ecm & ~mcm) // when character is at cursor position and in Standard Character mode, load the invert of character mask
pixelshiftreg<=waitingpixels^8'hFF;
else pixelshiftreg<=waitingpixels;
pixelattr<=waitingattr; // latch attribute and charpointer for pixelgenerator
pixelchar<=waitingchar;
end
else
begin
if(~multicolor)
pixelshiftreg<={pixelshiftreg[6:0],1'b0};
else if(doubleshift) // double pixel shifting
pixelshiftreg<={pixelshiftreg[5:0],2'b0};
end
end
end
else pixelshiftreg<=0; // clear shiftreg at the end of screen line to avoid shifting in its content at next line
end
assign pixelscreen=(csel)?widescreen:narrowscreen; // change between narrow and wide screens plus 1 pixel delay due to latch
assign multicolor= mcm & (ecm | pixelattr[3] | bmm); // multicolor rendering is initiated when mcm=1 and either ecm,bmm or character attribute's 4th bit is 1
always @* // video pixel color generator
begin
pixelcolor=bgcolor0;
if(pixelscreen & enabledisplay)
begin
if (~bmm & ~ecm) // Standard and Multicolor Character modes
begin
if(~multicolor) // Standard Character mode
begin
if((reverse|mcm)?pixelshiftreg[7]:(pixelshiftreg[7]& ~(pixelattr[7] & FlashCount[4]))^pixelchar[7])
pixelcolor=pixelattr[6:0];
end
else
begin // Multicolor Character mode
case(pixelshiftreg[7:6])
2'b00: pixelcolor=bgcolor0;
2'b01: pixelcolor=bgcolor1;
2'b10: pixelcolor=bgcolor2;
2'b11: pixelcolor={pixelattr[6:4],1'b0,pixelattr[2:0]};
endcase
end
end
else if (~mcm & ~bmm & ecm) // Extended Color Character mode
begin
if(pixelshiftreg[7])
pixelcolor=pixelattr[6:0];
else begin
case(pixelchar[7:6])
2'b00: pixelcolor=bgcolor0;
2'b01: pixelcolor=bgcolor1;
2'b10: pixelcolor=bgcolor2;
2'b11: pixelcolor=bgcolor3;
endcase
end
end
else if(~mcm & bmm & ~ecm) // Standard Bitmap mode
begin
if(pixelshiftreg[7])
pixelcolor={pixelattr[2:0],pixelchar[7:4]};
else pixelcolor={pixelattr[6:4],pixelchar[3:0]};
end
else if(mcm & bmm & ~ecm) // Multicolor bitmap mode
begin
case(pixelshiftreg[7:6])
2'b00: pixelcolor=bgcolor0;
2'b01: pixelcolor={pixelattr[2:0],pixelchar[7:4]};
2'b10: pixelcolor={pixelattr[6:4],pixelchar[3:0]};
2'b11: pixelcolor=bgcolor1;
endcase
end
else // invalid mode
begin
pixelcolor=7'b0;
end
end
else
pixelcolor=excolor;
end
always @(posedge clk) // latch pixelcolor and multiplex it with blank signal
begin
if (tick8)
if(~blanking)
colorreg<=pixelcolor;
else colorreg<=0;
end
//---------------------------------------------------------------------------
// Screen signals generation
//---------------------------------------------------------------------------
// PAL/NTSC screen constants
assign pal = !tvmode ? !palreg : tvmode[0];
assign EOS = pal?9'd311:9'd261; // End of Screen scanline
assign VS_START = pal?9'd254:9'd229; // Vertical sync start
assign VS_STOP = pal?9'd257:9'd232; // Vertical sync stop
assign EQ_START = pal?9'd251:9'd226; // Equalization start
assign EQ_STOP = pal?9'd260:9'd235; // Equalization stop
assign VBLANK_START = pal?9'd251:9'd226; // Screen blanking start
assign VBLANK_STOP = pal?9'd269:9'd244; // Screen blanking stop// Composite Sync signal
always @(posedge clk) // composite synchron is either hsync or equalization+vsync
begin
csyncreg<=(equalization)?(eq1&eq2)^vsync:hsync;
end
always @(posedge clk) // vsync signal inverts equalization signal
begin
if (videoline==VS_START && hpos_400)
vsync<=1;
else if (videoline==VS_STOP && hpos_400)
vsync<=0;
end
always @(posedge clk) // equalization signal active during actual vsync+equalization window
begin
if(videoline==EQ_START && hpos_400)
equalization<=1;
else if (videoline==EQ_STOP && hpos_400)
equalization<=0;
end
always @(posedge clk) // Equalization pulses generated by horizontal decoder events
begin
if(hpos_154)
eq1<=0;
else if (hpos_172)
eq1<=1;
if(hpos_382)
eq2<=0;
else if (hpos_400)
eq2<=1;
end
always @(posedge clk) // Horizontal sync pulse (due to original HMOS technology signal change takes 2 pixels long thus these change positions differ from the specification)
begin
if(hpos_359)
hsync<=0;
else if (hpos_391)
hsync<=1;
end
always @(posedge clk) begin // horizontal blanking zone
if((wide && hpos_1) || (~wide && hpos_424)) begin
if(videoline==VBLANK_STOP) vblank_out<=0;
hblank<=0;
end else if((wide && hpos_321) || (~wide && hpos_352)) begin // in real TED it starts at 352 but slew rate takes 2 pixels. 353 is at halfway. FIXME: Might be initiated at 344.
if(videoline==VBLANK_START) vblank_out<=1;
hblank<=1;
end
end
always @(posedge clk) // vertical blanking zone
begin
if(videoline==VBLANK_STOP)
vblank<=0;
else if(videoline==VBLANK_START)
vblank<=1;
end
assign blanking=hblank|vblank;
assign csync=csyncreg;
assign color=colorreg;
//-----------------------------------------------------------------------------------------------
// Memory Controller
//-----------------------------------------------------------------------------------------------
always @(posedge clk) // Generating RAS, internal CAS and MUX signals based on clk28 cycle numbers. Not 100% precise reproduction of original TED timing but still in dram specifications
case (phicounter) // one clk28 cycle is 35.35ns
1: begin
cs_io<=1;
cs_ram<=1;
cs0<=1;
cs1<=1;
end
7: if(io) cs_io<=0;
else if(~tedreg) begin
cs_ram<=0;
if(rw & ((~ramen & ~dotfetch_reg) | (charrom & dotfetch_reg ))) begin // ROM chip select is controlled by ramen register or by charrom register depending on whether dot data is fetched from bus
cs0<=~lowrom; // Basic area
cs1<=~highrom; // Kernal area
cs_ram<=lowrom|highrom; // RAM
end
end
endcase
// Generating memory area flags.
assign lowrom=(addr_in[15:14]==2'b10); //$8000-$bfff low rom area (Basic)
assign highrom=(addr_in[15:14]==2'b11); //$c000-$ffff high rom area (Kernal, IO and TED area)
assign io=(addr_in[15:8]==8'hFD || addr_in[15:8]==8'hFE); //$fd00-$feff IO space
assign tedreg=(addr_in[15:6]==10'b1111111100 && (addr_in[5]==0 || addr_in[5:1]==7'b11111)); //$ff00-$ff1f & $ff3e-$ff3f TED registers
//-----------------------------------------------------------------------------------------------
// Generating TED address out
//-----------------------------------------------------------------------------------------------
assign addr_out=(~aec)?addr_out_reg:16'hffff;
always @(posedge clk)
begin
if(cycle_end)
begin
addr_out_reg<=tedaddress;
dotfetch_reg<=dotfetch;
end
end
assign charpointer=(inc_videocounter)?((badline2)?data_in:char_buf[39]):8'd0;
//assign attrpointer=attr_buf[39];
always @*
begin
tedaddress=16'hffff;
dotfetch=0;
if(phi==0) // generating address for phi1 phase (will be clocked and valid in phi1)
begin
if(dma_state==TDMA)
tedaddress={vmbase,(badline)?1'b0:1'b1,videocounter}; // attribute or character pointer fetch address
end
else //if(~test)
begin // generating address for phi0 phase (will be clocked and valid in phi0)
if(refresh_inc|stopreg) // dram refresh address
tedaddress={8'hff,refreshcounter};
else if(inc_charpos & char_fetch)
begin
dotfetch=1;
if(~bmm) // Text mode fetch address
begin
tedaddress=(~reverse)?{charbase[5:0],charpointer[6:0],VertSubCount}:{charbase[5:1],charpointer,VertSubCount};
tedaddress[10:9]=(ecm)?2'b00:tedaddress[10:9];
end
else
tedaddress={bmapbase,CharPosition,VertSubCount}; // bitmap mode fetch address
end
end
/*
else begin // IC test mode fetch addresses
dotfetch=1;
if(~bmm)
tedaddress={5'h18,attrpointer,VertSubCount}; // test mode character screen
else tedaddress={3'b111,(CharPosition && {2'b11,attrpointer}),VertSubCount};
end
*/
end
//-----------------------------------------------------------------------------------------------
// TED registers write
//-----------------------------------------------------------------------------------------------
assign tedwrite=tedreg&~rw&cycle_end; // It signals TED register write which happens always when rw is low and end of double clock cycle
assign tedlatch=tedwrite_delay & (phicounter==3); // trying to simulate when exactly the hcounter is written by TED
always @(posedge clk)
begin
if(tedwrite)
tedwrite_delay<=1;
else if (phicounter==3)
tedwrite_delay<=0;
end
always @(posedge clk)
begin
if(reset) ramen <= 0;
resetRasterIrq<=1'b0;
//resetLpIrq<=1'b0;
resetCnt1Irq<=1'b0;
resetCnt2Irq<=1'b0;
resetCnt3Irq<=1'b0;
if (tedwrite) // when TED registers are addressed
begin
data_in_reg<=data_in;
addr_in_reg<=addr_in[5:0];
case(addr_in[5:0])
CONTROL1: // $FF06
begin
test<=data_in[7];
ecm_reg<=data_in[6];
bmm_reg<=data_in[5];
den<=data_in[4];
rsel<=data_in[3];
yscroll<=data_in[2:0];
end
CONTROL2: // $FF07
begin
reverse_reg<=data_in[7];
palreg<=data_in[6];
stop<=data_in[5];
mcm_reg<=data_in[4];
csel<=data_in[3];
xscroll<=data_in[2:0];
end
KEYLATCH: // $FF08
keylatch<=k[7:0];
IRQ: // $FF09
begin
resetCnt3Irq<=data_in[6];
resetCnt2Irq<=data_in[4];
resetCnt1Irq<=data_in[3];
//resetLpIrq<=data_in[2];
resetRasterIrq<=data_in[1];
end
IRQEN: // $FF0A
begin
enCnt3Irq<=data_in[6];
enCnt2Irq<=data_in[4];
enCnt1Irq<=data_in[3];
enRasterIrq<=data_in[1];
RasterCmp[8]<=data_in[0];
end
RASTER: // $FF0B
RasterCmp[7:0]<=data_in;
CURPOSHI: // $FF0C
CursorPos[9:8]<=data_in[1:0];
CURPOSLO: // $FF0D
CursorPos[7:0]<=data_in;
CH1FREQLO: // $FF0E
Ch1Freq[7:0]<=data_in;
CH2FREQLO: // $FF0F
Ch2Freq[7:0]<=data_in;
CH2FREQHI: // $FF10
Ch2Freq[9:8]<=data_in[1:0];
SOUNDCTRL: // $FF11
begin
damode<=data_in[7];
ch2noise<=data_in[6];
ch2en<=data_in[5];
ch1en<=data_in[4];
volume<=data_in[3:0];
end
BMAPBASE: // $FF12
begin
bmapbase<=data_in[5:3];
charrom<=data_in[2];
Ch1Freq[9:8]<=data_in[1:0];
end
CHARBASE: // $FF13
begin
charbase<=data_in[7:2];
clkmode<=data_in[1];
end
VIDEOBASE: // $FF14
vmbase<=data_in[7:3];
BGCOLOR0: // $FF15 , color change at cycle start, emulating white pixel bug (for all 5 color registers)
bgcolor0<=7'h7f;
BGCOLOR1: // $FF16
bgcolor1<=7'h7f;
BGCOLOR2: // $FF17
bgcolor2<=7'h7f;
BGCOLOR3: // $FF18
bgcolor3<=7'h7f;
EXCOLOR: // $FF19
excolor<=7'h7f;
ROMEN: ramen<=1'b0;
RAMEN: ramen<=1'b1;
default:;
endcase
end
// Color registers write (white pixel bug emulation)
else if (tedlatch) // these events happen 1 pixel later after cycle start, setting the proper color to color registers
case(addr_in_reg[5:0])
BGCOLOR0: // $FF15
bgcolor0<=data_in_reg[6:0];
BGCOLOR1: // $FF16
bgcolor1<=data_in_reg[6:0];
BGCOLOR2: // $FF17
bgcolor2<=data_in_reg[6:0];
BGCOLOR3: // $FF18
bgcolor3<=data_in_reg[6:0];
EXCOLOR: // $FF19
excolor<=data_in_reg[6:0];
default:;
endcase
end
// TED register read
always @(posedge clk)
begin
if(tedreg & rw)
begin
if(phicounter==7) // latch register contents to dataout reg at mux change
begin
case(addr_in[5:0])
TIMER1LO: // $FF00
dataout_reg<=timer1[7:0];
TIMER1HI: // $FF01
dataout_reg<=timer1[15:8];
TIMER2LO: // $FF02
dataout_reg<=timer2[7:0];
TIMER2HI: // $FF03
dataout_reg<=timer2[15:8];
TIMER3LO: // $FF04
dataout_reg<=timer3[7:0];
TIMER3HI: // $FF05
dataout_reg<=timer3[15:8];
CONTROL1: // $FF06
begin
dataout_reg[7]<=test;
dataout_reg[6]<=ecm;
dataout_reg[5]<=bmm;
dataout_reg[4]<=den;
dataout_reg[3]<=rsel;
dataout_reg[2:0]<=yscroll;
end
CONTROL2: // $FF07
begin
dataout_reg[7]<=reverse;
dataout_reg[6]<=~pal;
dataout_reg[5]<=stop;
dataout_reg[4]<=mcm;
dataout_reg[3]<=csel;
dataout_reg[2:0]<=xscroll;
end
KEYLATCH: // $FF08
begin
dataout_reg<=keylatch;
end
IRQ: // $FF09
begin
dataout_reg[7]<=~irq;
dataout_reg[6]<=Cnt3Irq;
dataout_reg[4]<=Cnt2Irq;
dataout_reg[3]<=Cnt1Irq;
dataout_reg[2]<=LPIrq; // Lightpen irq is always 1 as it is not implemented in TED
dataout_reg[1]<=RasterIrq;
end
IRQEN: // $FF0A
begin
dataout_reg[6]<=enCnt3Irq;
dataout_reg[4]<=enCnt2Irq;
dataout_reg[3]<=enCnt1Irq;
dataout_reg[2]<=enLPIrq; // lightpen irq enable bit is implemented in TED
dataout_reg[1]<=enRasterIrq;
dataout_reg[0]<=RasterCmp[8];
end
RASTER: // $FF0B
dataout_reg<=RasterCmp[7:0];
CURPOSHI: // $FF0C
dataout_reg[1:0]<=CursorPos[9:8];
CURPOSLO: // $FF0D
dataout_reg<=CursorPos[7:0];
CH1FREQLO: // $FF0E
dataout_reg<=Ch1Freq[7:0];
CH2FREQLO: // $FF0F
dataout_reg<=Ch2Freq[7:0];
CH2FREQHI: // $FF10
begin
dataout_reg[7]<=1'b0; // the 8th unused bit is always 0
dataout_reg[1:0]<=Ch2Freq[9:8];
end
SOUNDCTRL: //$FF11
begin
dataout_reg[7]<=damode;
dataout_reg[6]<=ch2noise;
dataout_reg[5]<=ch2en;
dataout_reg[4]<=ch1en;
dataout_reg[3:0]<=volume;
end
BMAPBASE: // $FF12
begin
dataout_reg[5:3]<=bmapbase;
dataout_reg[2]<=charrom;
dataout_reg[1:0]<=Ch1Freq[9:8];
end
CHARBASE: // $FF13
begin
dataout_reg[7:2]<=charbase;
dataout_reg[1]<=clkmode;
dataout_reg[0]<=~ramen;
end
VIDEOBASE: // $FF14
dataout_reg[7:3]<=vmbase;
BGCOLOR0: // $FF15
dataout_reg[6:0]<=bgcolor0;
BGCOLOR1: // $FF16
dataout_reg[6:0]<=bgcolor1;
BGCOLOR2: // $FF17
dataout_reg[6:0]<=bgcolor2;
BGCOLOR3: // $FF18
dataout_reg[6:0]<=bgcolor3;
EXCOLOR: // $FF19
dataout_reg[6:0]<=excolor;
CHARPOSRELOADHI: //$FF1A
dataout_reg[1:0]<=CharPosReload[9:8];
CHARPOSRELOADLO: //$FF1B
dataout_reg<=CharPosReload[7:0];
VSCANPOSHI: // $FF1C
dataout_reg[0]<=vcounter[8];
VSCANPOSLO: // $FF1D
dataout_reg<=vcounter[7:0];
HSCANPOS: // $FF1E
dataout_reg<={hcounter[8:2],1'b0};
FLASH_VERTSUB: //$FF1F
begin
dataout_reg[6:3]<=FlashCount[3:0];
dataout_reg[2:0]<=VertSubCount;
end
ROMEN: // $FF3E
dataout_reg<=8'h00;
RAMEN: // $FF3F
dataout_reg<=8'h00;
default:;
endcase
end
else if(phicounter==10) // put dataout register content to databus at this moment
datahold<=1;
end
if(phicounter==1)
begin
datahold<=0;
dataout_reg<=8'hff;
end
end
assign data_out=(datahold)?dataout_reg:8'hff;
//--------------------------------------------------------------------------------
// TED audio generator
//--------------------------------------------------------------------------------
assign snd=(ch1audio ? digivolume : 5'd0)+(ch2audio ? digivolume : 5'd0); // mixing audio channel signals
always @(posedge clk) // audio cycle counter divides single clock by 4
begin
if(single_cycle_end)
audiocycle<=audiocycle+1'd1;
end
assign ch1clk=single_cycle_end&(audiocycle==2'b11); // Channel1 clock
assign ch2clk=single_cycle_end&(audiocycle==2'b01); // Channel2 clock
// Channel 1
always @(posedge clk)
begin
if(ch1clk)
begin
if((ch1count==10'h3ff) || damode)
ch1count<=Ch1Freq+1'd1;
else ch1count<=ch1count+1'd1;
end
end
assign ch1stateclk=(ch1count==10'h3ff)?1'b1:1'b0;
always @(posedge clk) // Channel 1 state clock rising edge detection
begin
ch1stateclk_prev<=ch1stateclk;
if(damode|watchdog_ch1max) // reset ch1state if damode is enabled or watchdog timer expires
ch1state<=0;
else if(~ch1stateclk_prev & ch1stateclk) // if rising edge
ch1state<=~ch1state; // change channel 1 state
end
assign ch1audio=(ch1en)?~ch1state:1'b0; // ch1audio before D/A conversion
always @(posedge clk) // emulating dynamic latch behaviour using watchdog timer (forgets setting after 188416 * audio clock cycles)
begin
if((~ch1stateclk_prev & ch1stateclk)|watchdog_ch1max) // reset watchdog timer at channel1 state change or when maximum time reached
watchdog_ch1<=0;
else if(ch1clk) // watchdog timer counts with audio clock cycles
watchdog_ch1<=watchdog_ch1+1'd1;
end
assign watchdog_ch1max=(watchdog_ch1==18'd188416)?1'b1:1'b0;
// Channel 2
always @(posedge clk)
begin
if(ch2clk)
begin
if((ch2count==10'h3ff) || damode)
ch2count<=Ch2Freq+1'd1;
else ch2count<=ch2count+1'd1;
end
end
assign ch2stateclk=(ch2count==10'h3ff)?1'b1:1'b0;
always @(posedge clk) // Channel 2 state clock rising edge detection
begin
ch2stateclk_prev<=ch2stateclk;
if(damode) // reset ch2state if damode is enabled
ch2state<=0;
else if(~ch2stateclk_prev & ch2stateclk) // if rising edge
ch2state<=~ch2state; // change channel 2 state
end
assign ch2audio=(ch2en)?~ch2state:noise; // ch2audio combined with noise before D/A conversion
always @(posedge clk) // emulating dynamic latch behaviour using watchdog timer (forgets setting after 188416 * audio clock cycles)
begin
if((~ch2stateclk_prev & ch2stateclk)|watchdog_ch2max) // reset watchdog timer at channel1 state change or when maximum time reached
watchdog_ch2<=0;
else if(ch2clk) // watchdog timer counts with audio clock cycles
watchdog_ch2<=watchdog_ch2+1'd1;
end
assign watchdog_ch2max=(watchdog_ch2==18'd188416)?1'b1:1'b0;
// Noise generator
always @(posedge clk)
begin
if(damode)
noisegen<=0;
else if(~ch2stateclk_prev & ch2stateclk)
begin
for(n=1;n<8;n=n+1)
begin
noisegen[n]<=noisegen[n-1];
end
noisegen[0]<=1'b1^noisegen[7]^noisegen[5]^noisegen[4]^noisegen[1];
end
end
assign noise=(ch2noise)?noisegen[0]:1'b0; // noise signal
always @* // volume value conversion to pwmcounter numbers where PWM signal high value starts
begin
case (volume)
0: digivolume=0;
1: digivolume=1;
2: digivolume=3;
3: digivolume=5;
4: digivolume=7;
5: digivolume=9;
6: digivolume=11;
7: digivolume=13;
default: digivolume=15;
endcase
end
endmodule