// simulate fpga top-level with external dram, rom, z80
// (c) 2010-2016 NedoPC
`include "../include/tune.v"
//`define ZLOG 1
`define HALF_CLK_PERIOD (17.8)
`define ZCLK_DELAY (9.5)
// toshibo
//`define Z80_DELAY_DOWN (17.0)
//`define Z80_DELAY_UP (22.0)
// z0840008
`define Z80_DELAY_DOWN 34
`define Z80_DELAY_UP 30
module tb;
reg fclk;
wire clkz_out,clkz_in;
reg iorq_n,mreq_n,rd_n,wr_n; // has some delays relative to z*_n (below)
reg m1_n,rfsh_n; //
wire res; //
tri1 ziorq_n,zmreq_n,zrd_n,zwr_n,zm1_n,zrfsh_n; // connected to Z80
tri1 int_n,wait_n,nmi_n;
wire zint_n,zwait_n,znmi_n;
wire [15:0] #((`Z80_DELAY_DOWN+`Z80_DELAY_UP)/2) za;
wire [ 7:0] #((`Z80_DELAY_DOWN+`Z80_DELAY_UP)/2) zd;
// wire [15:0] za;
// wire [ 7:0] zd;
tri1 [ 7:0] zd_dut_to_z80;
// wire [ 7:0] zd_z80_to_dut;
reg [15:0] reset_pc = 16'h0000;
reg [15:0] reset_sp = 16'hFFFF;
wire csrom, romoe_n, romwe_n;
wire rompg0_n, dos_n;
wire rompg2,rompg3,rompg4;
wire [15:0] rd;
wire [9:0] ra;
wire rwe_n,rucas_n,rlcas_n,rras0_n,rras1_n;
tri0 [15:0] ide_d;
wire hsync,vsync;
wire [1:0] red,grn,blu;
// sdcard
wire sdcs_n, sddo, sddi, sdclk;
// avr
wire spick, spidi, spido, spics_n;
assign zwait_n = (wait_n==1'b0) ? 1'b0 : 1'b1;
assign znmi_n = (nmi_n==1'b0) ? 1'b0 : 1'b1;
assign zint_n = (int_n==1'b0) ? 1'b0 : 1'b1;
initial
begin
#100000000.0;
force DUT.video_top.zxborder = 4'd3;
end
initial
begin
fclk = 1'b0;
forever #`HALF_CLK_PERIOD fclk = ~fclk;
end
assign #`ZCLK_DELAY clkz_in = ~clkz_out;
top DUT( .fclk(fclk),
.clkz_out(clkz_out),
.clkz_in(clkz_in),
// z80
.iorq_n(iorq_n),
.mreq_n(mreq_n),
.rd_n(rd_n),
.wr_n(wr_n),
.m1_n(m1_n),
.rfsh_n(rfsh_n),
.int_n(int_n),
.nmi_n(nmi_n),
.wait_n(wait_n),
.res(res),
//
.d(zd),
.a(za),
// ROM
.csrom(csrom),
.romoe_n(romoe_n),
.romwe_n(romwe_n),
.rompg0_n(rompg0_n),
.dos_n(dos_n),
.rompg2(rompg2),
.rompg3(rompg3),
.rompg4(rompg4),
// DRAM
.rd(rd),
.ra(ra),
.rwe_n(rwe_n),
.rucas_n(rucas_n),
.rlcas_n(rlcas_n),
.rras0_n(rras0_n),
.rras1_n(rras1_n),
// ZX-bus
.iorqge1(1'b0),
.iorqge2(1'b0),
// IDE
.ide_d(ide_d),
.ide_rdy(1'b1),
// VG93
.step(1'b0),
.vg_sl(1'b0),
.vg_sr(1'b0),
.vg_tr43(1'b0),
.rdat_b_n(1'b1),
.vg_wf_de(1'b0),
.vg_drq(1'b1),
.vg_irq(1'b1),
.vg_wd(1'b0),
// SDcard SPI
.sddi(sddi),
.sddo(sddo),
.sdcs_n(sdcs_n),
.sdclk(sdclk),
// ATmega SPI
.spics_n(spics_n),
.spick(spick),
.spido(spido),
.spidi(spidi),
.vhsync(hsync),
.vvsync(vsync),
.vred(red),
.vgrn(grn),
.vblu(blu)
);
// assign zd_dut_to_z80 = tb.DUT.ena_ram ? tb.DUT.dout_ram : ( tb.DUT.ena_ports ? tb.DUT.dout_ports : ( tb.DUT.drive_ff ? 8'hFF : 8'bZZZZZZZZ ) );
assign zd_dut_to_z80 = tb.DUT.d_ena ? tb.DUT.d_pre_out : 8'bZZZZ_ZZZZ;
wire zrst_n = ~res;
T80a z80( .RESET_n(zrst_n),
.CLK_n(clkz_in),
.WAIT_n(zwait_n),
.INT_n(zint_n),
.NMI_n(znmi_n),
.M1_n(zm1_n),
.RFSH_n(zrfsh_n),
.MREQ_n(zmreq_n),
.IORQ_n(ziorq_n),
.RD_n(zrd_n),
.WR_n(zwr_n),
.BUSRQ_n(1'b1),
.A(za),
// .D(zd),
.D_I(zd_dut_to_z80),
.D_O(zd),
.ResetPC(reset_pc),
.ResetSP(reset_sp)
);
// now make delayed versions of signals
//
reg mreq_wr_n;
wire iorq_wr_n, full_wr_n;
//
// first, assure there is no X's at the start
//
initial
begin
m1_n = 1'b1;
rfsh_n = 1'b1;
mreq_n = 1'b1;
iorq_n = 1'b1;
rd_n = 1'b1;
wr_n = 1'b1;
mreq_wr_n = 1'b1;
end
//
always @(zm1_n)
if( zm1_n )
m1_n <= #`Z80_DELAY_UP zm1_n;
else
m1_n <= #`Z80_DELAY_DOWN zm1_n;
//
always @(zrfsh_n)
if( zrfsh_n )
rfsh_n <= #`Z80_DELAY_UP zrfsh_n;
else
rfsh_n <= #`Z80_DELAY_DOWN zrfsh_n;
//
always @(zmreq_n)
if( zmreq_n )
mreq_n <= #`Z80_DELAY_UP zmreq_n;
else
mreq_n <= #`Z80_DELAY_DOWN zmreq_n;
//
always @(ziorq_n)
if( ziorq_n )
iorq_n <= #`Z80_DELAY_UP ziorq_n;
else
iorq_n <= #`Z80_DELAY_DOWN ziorq_n;
//
always @(zrd_n)
if( zrd_n )
rd_n <= #`Z80_DELAY_UP zrd_n;
else
rd_n <= #`Z80_DELAY_DOWN zrd_n;
//
//
// special handling for broken T80 WR_n
//
always @(negedge clkz_in)
mreq_wr_n <= zwr_n;
//
assign iorq_wr_n = ziorq_n | (~zrd_n) | (~zm1_n);
//
assign full_wr_n = mreq_wr_n & iorq_wr_n;
//
// this way glitches won't affect state of wr_n
always @(full_wr_n)
if( !full_wr_n )
#`Z80_DELAY_DOWN wr_n <= full_wr_n;
else
#`Z80_DELAY_UP wr_n <= full_wr_n;
// ROM model
rom romko(
.addr( {rompg4,rompg3,rompg2,dos_n, (~rompg0_n), za[13:0]} ),
.data(zd_dut_to_z80),
.ce_n( romoe_n | (~csrom) )
);
// DRAM model
drammem dramko1(
.ma(ra),
.d(rd),
.ras_n(rras0_n),
.ucas_n(rucas_n),
.lcas_n(rlcas_n),
.we_n(rwe_n)
);
//
drammem dramko2(
.ma(ra),
.d(rd),
.ras_n(rras1_n),
.ucas_n(rucas_n),
.lcas_n(rlcas_n),
.we_n(rwe_n)
);
defparam dramko1._verbose_ = 0;
defparam dramko2._verbose_ = 0;
defparam dramko1._init_ = 0;
defparam dramko2._init_ = 0;
`ifndef GATE
// trace rom page
wire rma14,rma15;
assign rma14 = DUT.page[0][0];
assign rma15 = DUT.page[0][1];
always @(rma14 or rma15)
begin
// $display("at time %t us",$time/1000000);
// case( {rma15, rma14} )
// 2'b00: $display("BASIC 48");
// 2'b01: $display("TR-DOS");
// 2'b10: $display("BASIC 128");
// 2'b11: $display("GLUKROM");
// default: $display("unknown");
// endcase
// $display("");
end
// trace ram page
wire [5:0] rpag;
assign rpag=DUT.page[3][5:0];
always @(rpag)
begin
// $display("at time %t us",$time/1000000);
// $display("RAM page is %d",rpag);
// $display("");
end
// key presses/nmi/whatsoever
initial
begin
#1;
tb.DUT.zkbdmus.kbd = 40'd0;
tb.DUT.zkbdmus.kbd[36] = 1'b1;
@(negedge int_n);
@(negedge int_n);
tb.DUT.zkbdmus.kbd[36] = 1'b0;
end
/*
initial
begin : gen_nmi
reg [21:0] a;
#1000000000;
a = 22'h3FC066;
put_byte(a,8'hF5); a=a+1;
put_byte(a,8'hC5); a=a+1;
put_byte(a,8'hD5); a=a+1;
put_byte(a,8'hE5); a=a+1;
put_byte(a,8'h10); a=a+1;
put_byte(a,8'hFE); a=a+1;
put_byte(a,8'h14); a=a+1;
put_byte(a,8'h01); a=a+1;
put_byte(a,8'hFE); a=a+1;
put_byte(a,8'h7F); a=a+1;
put_byte(a,8'hED); a=a+1;
put_byte(a,8'h51); a=a+1;
put_byte(a,8'hED); a=a+1;
put_byte(a,8'h78); a=a+1;
put_byte(a,8'h1F); a=a+1;
put_byte(a,8'hDA); a=a+1;
put_byte(a,8'h6A); a=a+1;
put_byte(a,8'h00); a=a+1;
put_byte(a,8'hE1); a=a+1;
put_byte(a,8'hD1); a=a+1;
put_byte(a,8'hC1); a=a+1;
put_byte(a,8'hF1); a=a+1;
put_byte(a,8'hD3); a=a+1;
put_byte(a,8'hBE); a=a+1;
put_byte(a,8'hED); a=a+1;
put_byte(a,8'h45); a=a+1;
@(posedge fclk);
tb.DUT.slavespi.cfg0_reg_out[1] = 1'b1;
@(posedge fclk);
tb.DUT.slavespi.cfg0_reg_out[1] = 1'b0;
#64000000;
tb.DUT.zkbdmus.kbd[39] = 1'b1;
@(negedge int_n);
tb.DUT.zkbdmus.kbd[39] = 1'b0;
end
*/
`endif
`ifdef ZLOG
reg [ 7:0] old_opcode;
reg [15:0] old_opcode_addr;
wire [7:0] zdd = zd_dut_to_z80;
reg was_m1;
always @(zm1_n)
if( zm1_n )
was_m1 <= 1'b0;
else
was_m1 = 1'b1;
always @(posedge (zmreq_n | zrd_n | zm1_n | (~zrfsh_n)) )
if( was_m1 )
begin
if( (zdd!==old_opcode) || (za!==old_opcode_addr) )
begin
if( tb.DUT.z80mem.romnram )
// $display("Z80OPROM: addr %x, opcode %x, time %t",za,zdd,$time);
$display("Z80OPROM: addr %x, opcode %x",za,zdd);
else
// $display("Z80OPRAM: addr %x, opcode %x, time %t",za,zdd,$time);
$display("Z80OPRAM: addr %x, opcode %x",za,zdd);
end
old_opcode = zdd;
old_opcode_addr = za;
end
always @(posedge (zmreq_n | zrd_n | (~zm1_n) | (~zrfsh_n)) )
if( !was_m1 )
begin
if( tb.DUT.z80mem.romnram )
// $display("Z80RDROM: addr %x, rddata %x, time %t",za,zdd,$time);
$display("Z80RDROM: addr %x, rddata %x",za,zdd);
else
// $display("Z80RDRAM: addr %x, rddata %x, time %t",za,zdd,$time);
$display("Z80RDRAM: addr %x, rddata %x",za,zdd);
end
always @(posedge (zmreq_n | zwr_n | (~zm1_n) | (~zrfsh_n)) )
begin
if( tb.DUT.z80mem.romnram )
// $display("Z80WRROM: addr %x, wrdata %x, time %t",za,zd,$time);
$display("Z80WRROM: addr %x, wrdata %x",za,zd);
else
// $display("Z80WRRAM: addr %x, wrdata %x, time %t",za,zd,$time);
$display("Z80WRRAM: addr %x, wrdata %x",za,zd);
end
`endif
// turbo
`ifdef C7MHZ
initial
force tb.DUT.zclock.turbo = 2'b01;
`else
`ifdef C35MHZ
initial
force tb.DUT.zclock.turbo = 2'b00;
`endif
`endif
// raster type
`ifdef CCONTEND
initial
force tb.DUT.modes_raster = 2'b10;
`endif
`ifdef NMITEST2
`define M48K
initial
begin
int i,fd;
logic [7:0] ldbyte;
reset_pc=16'h8000;
reset_sp=16'h8000;
fd = $fopen("dimkanmi.bin","rb");
if( !fd )
begin
$display("Can't open 'dimkanmi.bin'!");
$stop;
end
i='h8000;
begin : load_loop
while(1)
begin
if( 1!=$fread(ldbyte,fd) ) disable load_loop;
put_byte_48k(i,ldbyte);
i=i+1;
end
end
$fclose(fd);
wait(res===1'b0);
#(0.2);
tb.DUT.zports.atm_turbo = 1'b1;
tb.DUT.zports.peff7_int[4] = 1'b0;
#(100000); // 100 us
//force nmi_n = 1'b0;
@(posedge fclk);
force tb.DUT.imm_nmi = 1'b1;
@(posedge fclk);
release tb.DUT.imm_nmi;
end
`endif
`ifdef NMITEST3
`define M48K
initial
begin
int i,fd;
logic [7:0] ldbyte;
reset_pc=16'h0068;
reset_sp=16'h8000;
#(0.1); // let M48K rom load execute
fd = $fopen("dimkarom.bin","rb");
if( !fd )
begin
$display("Can't open 'dimkarom.bin'!");
$stop;
end
i='h0066;
begin : load_loop
while(1)
begin
if( 1!=$fread(ldbyte,fd) ) disable load_loop;
tb.romko.zxevo_rom.mem[i]=ldbyte;
i=i+1;
end
end
$fclose(fd);
wait(res===1'b0);
#(0.2);
tb.DUT.zports.atm_turbo = 1'b1;
tb.DUT.zports.peff7_int[4] = 1'b0;
#(1000000); // 1 ms
//force nmi_n = 1'b0;
@(posedge fclk);
force tb.DUT.imm_nmi = 1'b1;
@(posedge fclk);
release tb.DUT.imm_nmi;
end
`endif
// port #FE monitor
wire fe_write;
assign fe_write = (za[7:0]==8'hFE) && !wr_n && !iorq_n;
always @(negedge fe_write)
$display("port #FE monitor: border is %d at %t",zd[2:0],$time());
always @(negedge nmi_n)
$display("nmi monitor: negative edge at %t",$time());
// start in 48k mode
`ifdef M48K
initial
begin : force_48k_mode
int i;
int fd;
fd = $fopen("48.rom","rb");
if( 16384!=$fread(tb.romko.zxevo_rom.mem,fd) )
begin
$display("Couldn't load 48k ROM!\n");
$stop;
end
$fclose(fd);
wait(res===1'b0);
#(0.1);
tb.DUT.zports.atm_turbo = 1'b0;
tb.DUT.zports.atm_pen = 1'b0;
tb.DUT.zports.atm_cpm_n = 1'b1;
tb.DUT.zports.atm_pen2 = 1'b0;
// tb.DUT.zports.pent1m_ram0_0 = 1'b0;
// tb.DUT.zports.pent1m_1m_on = 1'b0;
// tb.DUT.zports.pent1m_page = 'd0;
// tb.DUT.zports.pent1m_ROM = 1'b1;
tb.DUT.zdos.dos = 1'b0;
/* tb.DUT.page[0] = 'd0;
tb.DUT.page[1] = 'd5;
tb.DUT.page[2] = 'd2;
tb.DUT.page[3] = 'd0;
tb.DUT.romnram[0] = 1'b1;
tb.DUT.romnram[1] = 1'b0;
tb.DUT.romnram[2] = 1'b0;
tb.DUT.romnram[3] = 1'b0;*/
tb.DUT.instantiate_atm_pagers[0].atm_pager.pages[0] = 'd0;
tb.DUT.instantiate_atm_pagers[1].atm_pager.pages[0] = 'd5;
tb.DUT.instantiate_atm_pagers[2].atm_pager.pages[0] = 'd2;
tb.DUT.instantiate_atm_pagers[3].atm_pager.pages[0] = 'd0;
tb.DUT.instantiate_atm_pagers[0].atm_pager.pages[1] = 'd0;
tb.DUT.instantiate_atm_pagers[1].atm_pager.pages[1] = 'd5;
tb.DUT.instantiate_atm_pagers[2].atm_pager.pages[1] = 'd2;
tb.DUT.instantiate_atm_pagers[3].atm_pager.pages[1] = 'd0;
tb.DUT.instantiate_atm_pagers[0].atm_pager.ramnrom[0] = 'd0;
tb.DUT.instantiate_atm_pagers[1].atm_pager.ramnrom[0] = 'd1;
tb.DUT.instantiate_atm_pagers[2].atm_pager.ramnrom[0] = 'd1;
tb.DUT.instantiate_atm_pagers[3].atm_pager.ramnrom[0] = 'd1;
tb.DUT.instantiate_atm_pagers[0].atm_pager.ramnrom[1] = 'd0;
tb.DUT.instantiate_atm_pagers[1].atm_pager.ramnrom[1] = 'd1;
tb.DUT.instantiate_atm_pagers[2].atm_pager.ramnrom[1] = 'd1;
tb.DUT.instantiate_atm_pagers[3].atm_pager.ramnrom[1] = 'd1;
tb.DUT.zports.atm_scr_mode = 3'b011;
/* tb.DUT.peff7[5] = 1'b0;
tb.DUT.peff7[0] = 1'b0;
tb.DUT.p7ffd[3] = 1'b0;*/
// tb.DUT.zports.peff7[7] = 1'b0;
// tb.DUT.zports.peff7[0] = 1'b0;
// tb.DUT.zports.p7ffd[3] = 1'b0;
tb.DUT.zports.peff7_int = 8'h14;
tb.DUT.zports.p7ffd_int = 8'h30;
for(i=0;i<512;i=i+1)
begin : set_palette // R G B
tb.DUT.video_top.video_palframe.palette[i] = { (i[1]?{1'b1,i[3]}:2'b00), 1'b0, (i[2]?{1'b1,i[3]}:2'b00), 1'b0, (i[0]?{1'b1,i[3]}:2'b00) };
end
end
`endif
// load and start some code after we've reached "1982 Sinclair research ltd"
`ifdef START_LOAD
initial
begin
int i,fd;
logic [7:0] ldbyte;
wait( za==16'h15e0 && zmreq_n==1'b0 && zrd_n == 1'b0 );
$display("loading and starting...");
fd = $fopen(`START_NAME,"rb");
for(i=`START_ADDR;i<`START_ADDR+`START_LEN;i=i+1)
begin
if( 1!=$fread(ldbyte,fd) )
begin
$display("can't read byte from input file!");
$stop;
end
put_byte_48k(i,ldbyte);
end
$fclose(fd);
$display("load ok!");
reset_pc = 16'h9718;
reset_sp = 16'h6000;
@(posedge clkz_in);
force tb.zrst_n = 1'b0;
repeat(3) @(posedge clkz_in);
release tb.zrst_n;
@(posedge clkz_in);
reset_pc = 16'h0000;
reset_sp = 16'hFFFF;
end
`endif
// force fetch mode
// initial
// begin
// force tb.DUT.dramarb.bw = 2'b11;
//
// #(64'd2400000000);
//
// release tb.DUT.dramarb.bw;
// end
`ifndef NO_PIXER
// picture out
pixer pixer
(
.clk(fclk),
.vsync(vsync),
.hsync(hsync),
.red(red),
.grn(grn),
.blu(blu)
);
`endif
/*
// time ticks
always
begin : timemark
integer ms;
ms = ($time/1000000);
// $display("timemark %d ms",ms);
#10000000.0; // 1 ms
end
*/
// init dram
`ifndef NMITEST2
initial
begin : init_dram
integer i;
for(i=0;i<4*1024*1024;i=i+1)
begin
put_byte(i,(i%257));
end
end
`endif
// cmos simulation
wire [7:0] cmos_addr;
wire [7:0] cmos_read;
wire [7:0] cmos_write;
wire cmos_rnw;
wire cmos_req;
cmosemu cmosemu
(
.zclk(clkz_in),
.cmos_req (cmos_req ),
.cmos_addr (cmos_addr ),
.cmos_rnw (cmos_rnw ),
.cmos_read (cmos_read ),
.cmos_write(cmos_write)
);
assign cmos_req = tb.DUT.wait_start_gluclock;
assign cmos_rnw = tb.DUT.wait_rnw;
assign cmos_addr = tb.DUT.gluclock_addr;
assign cmos_write = tb.DUT.wait_write;
always @*
force tb.DUT.wait_read = cmos_read;
`ifdef SPITEST
// spitest printing module
// does not hurt at any time (yet), so attached forever
spitest_print spitest_print(
.sdclk (sdclk ),
.sddi (sddi ),
.sddo (sddo ),
.sdcs_n(sdcs_n)
);
// spitest AVR imitator
spitest_avr spitest_avr(
.spick (spick ),
.spics_n(spics_n),
.spido (spido ),
.spidi (spidi )
);
`else
assign sddi = 1'b1;
assign spics_n = 1'b1;
assign spick = 1'b0;
assign spido = 1'b1;
`endif
// // set up breakpoint
// initial
// begin
// #(650_000_000); // wait 650ms = 650*1000*1000 ns
//
// @(posedge fclk);
//
// tb.DUT.zports.brk_ena = 1'b1;
// tb.DUT.zports.brk_addr = 16'h0041;
// end
task put_byte;
input [21:0] addr;
input [ 7:0] data;
reg [19:0] arraddr;
begin
arraddr = { addr[21:12], addr[11:2] };
case( addr[1:0] ) // chipsel, bytesel
2'b00: tb.dramko1.array[arraddr][15:8] = data;
2'b01: tb.dramko1.array[arraddr][ 7:0] = data;
2'b10: tb.dramko2.array[arraddr][15:8] = data;
2'b11: tb.dramko2.array[arraddr][ 7:0] = data;
endcase
end
endtask
task put_byte_48k
(
input [15:0] addr,
input [ 7:0] data
);
case( addr[15:14] )
2'b01: put_byte(addr-16'h4000 + 22'h14000,data);
2'b10: put_byte(addr-16'h8000 + 22'h08000,data);
2'b11: put_byte(addr-16'hc000 + 22'h00000,data);
endcase
endtask
endmodule