// (c) NedoPC 2014
 
//
 
// top-level for testing pgmflash
 
 
 
`timescale 1ns/1ps
 
 
 
`define HALF_24MHZ (20.833)
 
`define HALF_FPGA  (50.000)
 
 
 
`define HALF_ZX (71.428)
 
 
 
 
 
module tb;
 
 
 
        reg clk_24mhz;
 
        reg clk_fpga;
 
        
 
        reg clk_zx;
 
 
 
 
 
 
 
 
 
 
 
        wire clksel0;
 
        wire clksel1;
 
 
 
        reg warmres_n;
 
 
 
 
 
        wire [ 7:0] d;
 
        wire [15:0] a;
 
 
 
        wire iorq_n;
 
        wire mreq_n;
 
        wire rd_n;
 
        wire wr_n;
 
        wire m1_n;
 
        wire int_n;
 
        wire nmi_n;
 
        wire busrq_n;
 
        reg  busak_n;
 
        tri1 z80res_n;
 
 
 
 
 
        wire mema14;
 
        wire mema15;
 
        wire mema16;
 
        wire mema17;
 
        wire mema18;
 
        wire [3:0] ramcs_n;
 
        wire mema21;
 
        wire romcs_n;
 
        wire memoe_n;
 
        wire memwe_n;
 
 
 
 
 
        tri1 [7:0] zxid;
 
        reg  [7:0] zxa;
 
        tri0 zxa14;
 
        tri0 zxa15;
 
        reg  zxiorq_n = 1'b1;
 
        reg  zxmreq_n = 1'b1;
 
        reg  zxrd_n   = 1'b1;
 
        reg  zxwr_n   = 1'b1;
 
        wire zxcsrom_n;
 
        wire zxblkiorq_n;
 
        wire zxblkrom_n;
 
        wire zxgenwait_n;
 
        wire zxbusin;
 
        wire zxbusena_n;
 
 
 
 
 
        wire dac_bitck;
 
        wire dac_lrck;
 
        wire dac_dat;
 
 
 
 
 
        wire sd_clk;
 
        wire sd_cs;
 
        wire sd_do;
 
        tri1 sd_di;
 
        tri1 sd_wp;
 
        tri1 sd_det;
 
 
 
 
 
        wire ma_clk;
 
        wire ma_cs;
 
        wire ma_do;
 
        tri1 ma_di;
 
 
 
        wire mp3_xreset;
 
        tri1 mp3_req;
 
        wire mp3_clk;
 
        wire mp3_dat;
 
        wire mp3_sync;
 
 
 
        wire led;
 
 
 
        int autoinc_ena = 0;
 
 
 
        
 
        tri1 [7:0] zxd;
 
 
 
        wire [7:0] zxin;
 
        reg  [7:0] zxout;
 
        reg        zxena;
 
 
 
 
 
 
 
        // rom read & write queues
 
        int wr_queue [$];
 
        int rd_addr_queue [$];
 
        int rd_reta_queue [$];
 
        int rd_retd_queue [$];
 
 
 
        int very_first_read = 1;
 
 
 
        int rom_addr  = 0;
 
        int rom_phase = 0;
 
 
 
 
 
        // zx databus
 
        assign zxin = zxd;
 
        assign zxd  = zxena ? zxout : 8'bZZZZ_ZZZZ;
 
 
 
 
 
        // 74*245 emulation
 
        assign zxd  = (!zxbusena_n && !zxbusin) ? zxid : 8'bZZZZ_ZZZZ;
 
        assign zxid = (!zxbusena_n &&  zxbusin) ? zxd  : 8'bZZZZ_ZZZZ;
 
 
 
 
 
        // busrq/busak logic emulation
 
        always @(posedge clk_fpga)
 
        if( !z80res_n )
 
                busak_n <= 1'b1;
 
        else if( !busrq_n )
 
                busak_n <= 1'b0;
 
        else
 
                busak_n <= 1'b1;
 
 
 
 
 
 
 
 
 
        // clock gen
 
        initial
 
        begin
 
                clk_24mhz = 1'b1;
 
                forever #(`HALF_24MHZ) clk_24mhz = ~clk_24mhz;
 
        end
 
        //
 
        initial
 
        begin
 
                clk_fpga = 1'b1;
 
                forever #(`HALF_FPGA) clk_fpga = ~clk_fpga;
 
        end
 
        //
 
        initial
 
        begin
 
                clk_zx = 1'b1;
 
                forever #(`HALF_ZX) clk_zx = ~clk_zx;
 
        end
 
 
 
 
 
 
 
        initial
 
        begin
 
                warmres_n = 1'b0;
 
                #(1);
 
                repeat(2) @(posedge clk_fpga);
 
                warmres_n <= 1'b1;
 
        end
 
 
 
 
 
 
 
        // DUT
 
        top top
 
        (
 
                .clk_fpga(clk_fpga),
 
                .clk_24mhz(clk_24mhz),
 
                .clksel0(clksel0),
 
                .clksel1(clksel1),
 
                .warmres_n(warmres_n),
 
                .d(d),
 
                .a(a),
 
                .iorq_n(iorq_n),
 
                .mreq_n(mreq_n),
 
                .rd_n(rd_n),
 
                .wr_n(wr_n),
 
                .m1_n(m1_n),
 
                .int_n(int_n),
 
                .nmi_n(nmi_n),
 
                .busrq_n(busrq_n),
 
                .busak_n(busak_n),
 
                .z80res_n(z80res_n),
 
                .mema14(mema14),
 
                .mema15(mema15),
 
                .mema16(mema16),
 
                .mema17(mema17),
 
                .mema18(mema18),
 
                .ram0cs_n(ramcs_n[0]),
 
                .ram1cs_n(ramcs_n[1]),
 
                .ram2cs_n(ramcs_n[2]),
 
                .ram3cs_n(ramcs_n[3]),
 
                .mema21(mema21),
 
                .romcs_n(romcs_n),
 
                .memoe_n(memoe_n),
 
                .memwe_n(memwe_n),
 
                .zxid(zxid),
 
                .zxa(zxa),
 
                .zxa14(zxa14),
 
                .zxa15(zxa15),
 
                .zxiorq_n(zxiorq_n),
 
                .zxmreq_n(zxmreq_n),
 
                .zxrd_n(zxrd_n),
 
                .zxwr_n(zxwr_n),
 
                .zxcsrom_n(zxcsrom_n),
 
                .zxblkiorq_n(zxblkiorq_n),
 
                .zxblkrom_n(zxblkrom_n),
 
                .zxgenwait_n(zxgenwait_n),
 
                .zxbusin(zxbusin),
 
                .zxbusena_n(zxbusena_n),
 
                .dac_bitck(dac_bitck),
 
                .dac_lrck(dac_lrck),
 
                .dac_dat(dac_dat),
 
                .sd_clk(sd_clk),
 
                .sd_cs(sd_cs),
 
                .sd_do(sd_do),
 
                .sd_di(sd_di),
 
                .sd_wp(sd_wp),
 
                .sd_det(sd_det),
 
                .ma_clk(ma_clk),
 
                .ma_cs(ma_cs),
 
                .ma_do(ma_do),
 
                .ma_di(ma_di),
 
                .mp3_xreset(mp3_xreset),
 
                .mp3_req(mp3_req),
 
                .mp3_clk(mp3_clk),
 
                .mp3_dat(mp3_dat),
 
                .mp3_sync(mp3_sync),
 
                .led_diag(led)
 
        );
 
 
 
 
 
 
 
 
 
        rom_emu rom_emu
 
        (
 
                .a   ({mema18,mema17,mema16,mema15,mema14,a[13:0]}),
 
                .d   (d),
 
                .ce_n(romcs_n),
 
                .oe_n(memoe_n),
 
                .we_n(memwe_n)
 
        );
 
 
 
 
 
        initial
 
        begin : test_sequence
 
 
 
                reg [7:0] tmp;
 
                int i;
 
 
 
                wait(warmres_n==1'b1);
 
                repeat(10) @(negedge clk_zx);
 
 
 
 
 
                // start polling for init_in_progress end.
 
                // first we poll floating bus (==ff), then init_in_progress==1, finally it sets to 0.
 
                init_wait();
 
 
 
                // make software init
 
                iowr(.addr(8'h33),.data(8'h80));
 
 
 
                // wait for end of init_in_progress again
 
                init_wait();
 
 
 
                // play with led
 
                led_test();
 
 
 
                // "presence check" check
 
                presence_check();
 
 
 
$display("rom access!");
 
                // check rom access
 
                rom_check();
 
 
 
 
 
 
 
                $display("TESTS PASSED!");
 
                $stop;
 
        end
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
        task init_wait;
 
 
 
                reg [7:0] tmp;
 
                int i;
 
 
 
                for(i=0;i<100;i=i+1)
 
                begin
 
                        iord(.addr(8'h33),.data(tmp));
 
                        if( !(tmp&8'h80) ) disable init_wait;
 
                end
 
 
 
                $display("init_wait() failed: too long waiting for init_in_progress going to 0!");
 
                $stop;
 
        endtask
 
 
 
        
 
        task led_test;
 
                
 
                // assume that led_test called after reset or init, so led initial state is known to be 0.
 
 
 
                int i;
 
                int led_state;
 
 
 
 
 
 
 
                if( led!==1'b0 )
 
                begin
 
                        $display("led is not 0 at the start of led_test!");
 
                        $stop;
 
                end
 
 
 
                // invert led several times and check
 
                led_state = 0;
 
                for(i=0;i<20;i=i+1)
 
                begin
 
                        iowr(.addr(8'h33),.data(8'h40));
 
                        
 
                        led_state = led_state ^ 1;
 
 
 
                        if( led!==led_state[0] )
 
                        begin
 
                                $display("led is not inverted properly after write of 0x40 to 0x33 in led_test!");
 
                                $stop;
 
                        end
 
                end
 
        endtask
 
 
 
 
 
        task presence_check;
 
 
 
                // assume we start after init, so test reg contains zeros. so check it first.
 
 
 
                reg [7:0] tmp;
 
 
 
                int i,treg,rnd;
 
 
 
 
 
                iord(.addr(8'h3B),.data(tmp));
 
 
 
                if( tmp!==8'd0 )
 
                begin
 
                        $display("test reg at first read is not zero!");
 
                        $stop;
 
                end
 
 
 
 
 
                treg = 0;
 
 
 
                for(i=0;i<256;i=i+1)
 
                begin
 
                        rnd = $random>>24;
 
 
 
                        iowr(.addr(8'h3B),.data(rnd[7:0]));
 
                        
 
                        iord(.addr(8'h3B),.data(tmp));
 
 
 
                        treg[8:0] = { ~rnd[7:0], treg[8] };
 
 
 
                        if( treg[7:0]!==tmp[7:0] )
 
                        begin
 
                                $display("test reg at read after write is wrong!");
 
                                $stop;
 
                        end
 
                end
 
 
 
        endtask
 
 
 
 
 
        task rom_check;
 
 
 
                int addr = 0;
 
                int wrdata;
 
                int rddata;
 
                reg [7:0] tmp;
 
                int rnd;
 
 
 
                int read_nwrite;
 
                int addr_init_num = (-1);
 
                int autoinc;
 
                int old_autoinc = 0;
 
                int access_num;
 
 
 
                int i;
 
 
 
 
 
                forever
 
                begin
 
                        // get some random numbers and decide what to do
 
 
 
                        read_nwrite = $random>>31;
 
 
 
                        if( addr_init_num<0 )
 
                                addr_init_num = 3;
 
                        else
 
                                addr_init_num = $random>>30;
 
 
 
                        autoinc = $random>>31;
 
 
 
                        access_num = 1 + ($random>>28); // 1..16
 
 
 
 
 
                        // start doing that
 
                        if( autoinc!=old_autoinc )
 
                        begin
 
                                iowr( .addr(8'h33), .data( autoinc ? 8'h20 : 8'h00 ) );
 
                                old_autoinc = autoinc;
 
                        end
 
 
 
                        for(i=0;i<addr_init_num;i=i+1)
 
                        begin
 
                                rnd = $random>>24;
 
 
 
                                addr[i*8 +: 8] = rnd[7:0];
 
                                iowr( .addr(8'hB3), .data( rnd[7:0] ) );
 
                        end
 
                        
 
                        if( read_nwrite )
 
                        begin : read
 
                                for(i=0;i<access_num;i=i+1)
 
                                        iord( .addr(8'hBB), .data(tmp) );
 
                        end
 
                        else 
 
                        begin : write
 
                                for(i=0;i<access_num;i=i+1)
 
                                        iowr( .addr(8'hBB), .data($random>>24) );
 
                        end
 
                end
 
 
 
        endtask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
        // IO cycles emulator
 
        task iord;
 
 
 
                input  [7:0] addr;
 
 
 
                output [7:0] data;
 
 
 
                begin
 
                        if( addr==8'hBB )
 
                        begin
 
                                rom_phase = 0;
 
 
 
                                rd_addr_queue.push_back(rom_addr);
 
                        end
 
                        
 
                        @(posedge clk_zx);
 
 
 
                        zxmreq_n <= 1'b1;
 
                        zxiorq_n <= 1'b1;
 
                        zxrd_n   <= 1'b1;
 
                        zxwr_n   <= 1'b1;
 
 
 
                        zxena <= 1'b0;
 
 
 
                        zxa <= addr;
 
 
 
                        @(negedge clk_zx);
 
 
 
                        zxiorq_n <= 1'b0;
 
                        zxrd_n   <= 1'b0;
 
 
 
                        @(negedge clk_zx);
 
                        @(negedge clk_zx);
 
 
 
                        data = zxin;
 
 
 
                        zxiorq_n <= 1'b1;
 
                        zxrd_n   <= 1'b1;
 
 
 
                        if( addr==8'hBB )
 
                        begin : check_read_rom
 
                                int taddr, tdata;
 
 
 
                                taddr = rd_reta_queue.pop_front();
 
 
 
                                if( taddr[18:0]!==rom_addr[18:0] )
 
                                begin
 
                                        $display("iord: rom addr error!");
 
                                        $display("iord: addr from queue: %h",taddr[18:0]);
 
                                        $display("iord: addr from bus:   %h",rom_addr[18:0]);
 
                                        $stop;
 
                                end
 
 
 
                                
 
                                if( !very_first_read )
 
                                begin
 
                                        tdata = rd_retd_queue.pop_front();
 
 
 
                                        if( tdata[7:0]!==data[7:0] )
 
                                        begin
 
                                                $display("iord: rom data error!");
 
                                                $display("iord: data from queue: %h",tdata[7:0]);
 
                                                $display("iord: data from bus:   %h",data[7:0]);
 
                                                $stop;
 
                                        end
 
                                end
 
                                else
 
                                begin
 
                                        very_first_read = 0;
 
                                end
 
                                
 
                                if( autoinc_ena ) rom_addr++;
 
                        end
 
                end
 
 
 
        endtask
 
        //
 
        task iowr;
 
 
 
                input [7:0] addr;
 
                input [7:0] data;
 
 
 
                begin
 
 
 
                        if( addr==8'h33 )
 
                        begin
 
                                autoinc_ena = data[5];
 
                        end
 
 
 
                        if( addr==8'hB3 )
 
                        begin
 
                                rom_addr[rom_phase*8 +: 8] = data[7:0];
 
                                rom_phase = (rom_phase>=2) ? 0 : (rom_phase+1);
 
                        end
 
                        
 
                        if( addr==8'hBB || (addr==8'h33 && (data & 8'h80)) )
 
                        begin
 
                                rom_phase = 0;
 
                        end
 
 
 
                        if( addr==8'hBB )
 
                        begin
 
                                wr_queue.push_back((rom_addr<<8)|data[7:0]);
 
                                if( autoinc_ena ) rom_addr++;
 
                        end
 
 
 
 
 
                        @(posedge clk_zx);
 
 
 
                        zxmreq_n <= 1'b1;
 
                        zxiorq_n <= 1'b1;
 
                        zxrd_n   <= 1'b1;
 
                        zxwr_n   <= 1'b1;
 
 
 
                        zxena <= 1'b1;
 
 
 
                        zxa   <= addr;
 
                        zxout <= data;
 
 
 
                        @(negedge clk_zx);
 
 
 
                        zxiorq_n <= 1'b0;
 
                        zxwr_n   <= 1'b0;
 
 
 
                        @(negedge clk_zx);
 
                        @(negedge clk_zx);
 
 
 
                        zxiorq_n <= 1'b1;
 
                        zxwr_n   <= 1'b1;
 
 
 
                        wait(zxwr_n==1'b1); // delta-cycle delay!!!
 
 
 
                        zxena <= 1'b0;
 
 
 
                end
 
 
 
        endtask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
endmodule
 
 
 
 
 
 
 
 
 
 
 
 
 
module rom_emu
 
(
 
        input  wire [18:0] a,
 
        inout  wire [ 7:0] d,
 
        input  wire        ce_n,
 
        input  wire        oe_n,
 
        input  wire        we_n
 
);
 
        wire rd_stb = ~(ce_n|oe_n);
 
        wire wr_stb = ~(ce_n|we_n);
 
 
 
        reg old_wr_stb;
 
 
 
        wire [7:0] dwr;
 
        wire [7:0] drd;
 
 
 
        reg [7:0]  read_data;
 
        reg [7:0] write_data;
 
 
 
        assign d = rd_stb ? drd : 8'bZZZZ_ZZZZ;
 
 
 
        assign dwr = d;
 
 
 
 
 
        
 
        always @(posedge rd_stb)
 
        if( rd_stb==1'b1 )
 
        begin : test_read
 
 
 
                int taddr;
 
 
 
                read_data = $random>>24;
 
 
 
                tb.rd_reta_queue.push_back(a[18:0]);
 
                tb.rd_retd_queue.push_back(read_data);
 
 
 
                taddr = tb.rd_addr_queue.pop_front();
 
 
 
                if( taddr[18:0]!==a[18:0] )
 
                begin
 
                        $display("rom_emu: read address error!");
 
                        $display("rom_emu: addr from queue: %h",taddr[18:0]);
 
                        $display("rom_emu: addr from bus:   %h",a[18:0]);
 
                        $stop;
 
                end
 
        end
 
        //
 
        assign drd = read_data;
 
 
 
 
 
        always @(wr_stb)
 
        if( wr_stb==1'b0 && old_wr_stb==1'b1 )
 
        begin : test_write
 
 
 
                int taddr;
 
                int tdata;
 
                int tqueue;
 
 
 
                tqueue = tb.wr_queue.pop_front();
 
 
 
                taddr = tqueue>>8;
 
                tdata = tqueue&255;
 
 
 
                if( taddr[18:0]!==a[18:0] )
 
                begin
 
                        $display("rom_emu: write address error!");
 
                        $stop;
 
                end
 
 
 
                if( tdata[7:0]!==dwr[7:0] )
 
                begin
 
                        $display("rom_emu: write data error!");
 
                        $stop;
 
                end
 
 
 
                old_wr_stb = wr_stb;
 
        end
 
 
 
 
 
 
 
 
 
endmodule