From a910263a8fca9664f9f5da910fdcb9f203f8c8f4 Mon Sep 17 00:00:00 2001 From: harbaum Date: Thu, 22 Jan 2015 19:21:13 +0000 Subject: [PATCH] Atari ST simulation --- tests/verilator/dma/Makefile | 22 + tests/verilator/dma/acsi.v | 120 +++++ tests/verilator/dma/dma.sav | 113 +++++ tests/verilator/dma/dma.v | 776 +++++++++++++++++++++++++++++++++ tests/verilator/dma/dma_tb.cpp | 731 +++++++++++++++++++++++++++++++ tests/verilator/dma/fdc.v | 272 ++++++++++++ 6 files changed, 2034 insertions(+) create mode 100644 tests/verilator/dma/Makefile create mode 100644 tests/verilator/dma/acsi.v create mode 100644 tests/verilator/dma/dma.sav create mode 100644 tests/verilator/dma/dma.v create mode 100644 tests/verilator/dma/dma_tb.cpp create mode 100644 tests/verilator/dma/fdc.v diff --git a/tests/verilator/dma/Makefile b/tests/verilator/dma/Makefile new file mode 100644 index 0000000..4f4f777 --- /dev/null +++ b/tests/verilator/dma/Makefile @@ -0,0 +1,22 @@ +PROJECT=dma +NOWARN = -Wno-UNOPTFLAT -Wno-WIDTH # --report-unoptflat # -Wno-UNOPTFLAT + +all: $(PROJECT).vcd + +obj_dir/stamp: $(PROJECT).v fdc.v $(PROJECT)_tb.cpp + verilator $(NOWARN) --cc --trace --exe $(PROJECT).v $(PROJECT)_tb.cpp + touch obj_dir/stamp + +obj_dir/V$(PROJECT): obj_dir/stamp + make -j -C obj_dir/ -f V$(PROJECT).mk V$(PROJECT) + +$(PROJECT).vcd: obj_dir/V$(PROJECT) + obj_dir/V$(PROJECT) + +clean: + rm -rf obj_dir + rm -f $(PROJECT).vcd + rm -f *~ + +view: $(PROJECT).vcd + gtkwave $< $(PROJECT).sav & diff --git a/tests/verilator/dma/acsi.v b/tests/verilator/dma/acsi.v new file mode 100644 index 0000000..38d1dda --- /dev/null +++ b/tests/verilator/dma/acsi.v @@ -0,0 +1,120 @@ +// acsi.v +// +// Atari ST ACSI implementation for the MIST baord +// http://code.google.com/p/mist-board/ +// +// Copyright (c) 2015 Till Harbaum +// +// This source file 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. +// +// This source file 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 . +// + +module acsi ( + // clocks and system interface + input clk, + input reset, + + input [7:0] enable, + + input dma_ack, // IO controller answers request + input dma_nak, // IO controller rejects request + input [7:0] dma_status, + + + input [2:0] status_sel, + output [7:0] status_byte, + + // cpu interface + input [1:0] cpu_addr, + input cpu_sel, + input cpu_rw, + input [7:0] cpu_din, + output [7:0] cpu_dout, + + output reg irq +); + +// acsi always returns dma status on cpu_read +assign cpu_dout = dma_status; + +reg [2:0] target; +reg [4:0] cmd; +reg [2:0] byte_counter; +reg [7:0] cmd_parms [4:0]; +reg busy; + +// acsi status as reported to the io controller +assign status_byte = + (status_sel == 0)?{ target, cmd }: + (status_sel == 1)?cmd_parms[0]: + (status_sel == 2)?cmd_parms[1]: + (status_sel == 3)?cmd_parms[2]: + (status_sel == 4)?cmd_parms[3]: + (status_sel == 5)?cmd_parms[4]: + (status_sel == 6)?{ 7'b0000000, busy }: + 8'h00; + +// CPU write interface +always @(negedge clk) begin + if(reset) begin + target <= 3'd0; + cmd <= 5'd0; + irq <= 1'b0; + busy <= 1'b0; + end else begin + + // DMA transfer has been ack'd by io controller + if(dma_ack && busy) begin + irq <= 1'b1; // set acsi irq + busy <= 1'd0; + end + + // DMA transfer has been rejected by io controller (no such device) + if(dma_nak) + busy <= 1'd0; + + // cpu is reading status register -> clear acsi irq + // status itself is returned by the io controller with the dma_ack + if(cpu_sel && cpu_rw) + irq <= 1'b0; + + // acsi register access + if(cpu_sel && !cpu_rw) begin + if(!cpu_addr[0]) begin + // a0 == 0 -> first command byte + target <= cpu_din[7:5]; + cmd <= cpu_din[4:0]; + byte_counter <= 3'd0; + + // check if this acsi device is enabled + if(enable[cpu_din[7:5]] == 1'b1) + irq <= 1'b1; + end else begin + // further bytes + cmd_parms[byte_counter] <= cpu_din[7:0]; + byte_counter <= byte_counter + 3'd1; + + // check if this acsi device is enabled + if(enable[target] == 1'b1) begin + // auto-ack first 5 bytes + if(byte_counter < 4) + irq <= 1'b1; + else + busy <= 1'b1; // request io cntroller + end + end + end + end +end + +endmodule // acsi diff --git a/tests/verilator/dma/dma.sav b/tests/verilator/dma/dma.sav new file mode 100644 index 0000000..0ea2a46 --- /dev/null +++ b/tests/verilator/dma/dma.sav @@ -0,0 +1,113 @@ +[*] +[*] GTKWave Analyzer v3.3.58 (w)1999-2014 BSI +[*] Mon Jan 12 13:59:53 2015 +[*] +[dumpfile] "/home/tharbaum/tmp/mist/tools/verilator_dma/dma.vcd" +[dumpfile_mtime] "Mon Jan 12 13:29:12 2015" +[dumpfile_size] 12524676 +[savefile] "/home/tharbaum/tmp/mist/tools/verilator_dma/dma.sav" +[timestart] 46436 +[size] 1183 936 +[pos] -1 -1 +*-10.775101 49392 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +[treeopen] TOP. +[treeopen] TOP.v. +[sst_width] 202 +[signals_width] 262 +[sst_expanded] 1 +[sst_vpaned_height] 497 +@28 +TOP.v.clk +TOP.v.reset +TOP.v.bus_cycle[1:0] +TOP.v.bus_cycle_L[1:0] +TOP.v.ss +TOP.v.sck +TOP.v.sdi +TOP.v.cycle_io +TOP.v.cycle_advance +@22 +TOP.v.fifo_wptr[3:0] +TOP.v.fifo(0)[15:0] +TOP.v.fifo(1)[15:0] +TOP.v.fifo(2)[15:0] +TOP.v.fifo(3)[15:0] +TOP.v.fifo(4)[15:0] +TOP.v.fifo(5)[15:0] +TOP.v.fifo(6)[15:0] +TOP.v.fifo(7)[15:0] +TOP.v.fifo(8)[15:0] +TOP.v.fifo(9)[15:0] +TOP.v.fifo(10)[15:0] +TOP.v.fifo(11)[15:0] +TOP.v.fifo(12)[15:0] +TOP.v.fifo(13)[15:0] +TOP.v.fifo(14)[15:0] +TOP.v.fifo(15)[15:0] +@28 +TOP.v.fifo_data_in_strobe +TOP.v.fifo_full +TOP.v.cpu_sel +TOP.v.cpu_rw +@22 +TOP.v.cpu_addr[2:0] +@28 +TOP.v.cpu_lds +@22 +TOP.v.cpu_din[15:0] +TOP.v.cpu_dout[15:0] +@24 +TOP.v.ram_br +@28 +[color] 2 +TOP.v.ram_write +@22 +TOP.v.dma_addr[22:0] +TOP.v.ram_dout[15:0] +@28 +[color] 1 +TOP.v.ram_read +[color] 1 +TOP.v.ram_read_done +@22 +TOP.v.ram_din[15:0] +@28 +TOP.v.dma_direction_in +TOP.v.dma_direction_set +@22 +TOP.v.sbuf[30:0] +@28 +TOP.v.dma_direction_in +[color] 5 +TOP.v.fifo_data_out_strobe +TOP.v.fifo_data_out_strobe_clk +@22 +TOP.v.fifo_rptr[3:0] +@28 +TOP.v.io_data_out_strobe +@29 +TOP.v.io_data_out_inc_strobe +@22 +TOP.v.fifo_data_out[15:0] +@28 +TOP.v.sck +@24 +TOP.v.cnt[5:0] +@22 +TOP.v.sdo_bit[3:0] +TOP.v.bcnt[4:0] +@28 +TOP.v.sdo +TOP.v.fifo_read_in_progress +TOP.v.fifo_read_cnt[2:0] +TOP.v.fifo_write_in_progress +@24 +TOP.v.fifo_write_cnt[2:0] +@28 +TOP.v.sector_strobe_prepare +TOP.v.sector_strobe +@22 +TOP.v.word_cnt[7:0] +TOP.v.dma_scnt[7:0] +[pattern_trace] 1 +[pattern_trace] 0 diff --git a/tests/verilator/dma/dma.v b/tests/verilator/dma/dma.v new file mode 100644 index 0000000..0adf4e8 --- /dev/null +++ b/tests/verilator/dma/dma.v @@ -0,0 +1,776 @@ +// dma.v +// +// Atari ST dma engine for the MIST baord +// http://code.google.com/p/mist-board/ +// +// This file implements a SPI client which can write data +// into any memory region. This is used to upload rom +// images as well as implementation the DMA functionality +// of the Atari ST DMA controller. +// +// This also implements the video adjustment. This has nothing +// to do with dma and should happen in user_io instead. +// But now it's here and moving it is not worth the effort. +// +// Copyright (c) 2014 Till Harbaum +// +// This source file 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. +// +// This source file 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 . +// + +// TODO: +// - Allow DMA transfers to ROM only for IO controller initiated transfers + +// ##############DMA/WD1772 Disk controller ########### +// -------+-----+-----------------------------------------------------+---------- +// $FF8600| |Reserved | +// $FF8602| |Reserved | +// $FF8604|word |FDC access/sector count |R/W +// $FF8606|word |DMA mode/status BIT 2 1 0|R +// | |Condition of FDC DATA REQUEST signal -----------' | || +// | |0 - sector count null,1 - not null ---------------' || +// | |0 - no error, 1 - DMA error ------------------------'| +// $FF8606|word |DMA mode/status BIT 8 7 6 . 4 3 2 1 .|W +// | |0 - read FDC/HDC,1 - write ---------' | | | | | | | | +// | |0 - HDC access,1 - FDC access --------' | | | | | | | +// | |0 - DMA on,1 - no DMA ------------------' | | | | | | +// | |Reserved ---------------------------------' | | | | | +// | |0 - FDC reg,1 - sector count reg -----------' | | | | +// | |0 - FDC access,1 - HDC access ----------------' | | | +// | |0 - pin A1 low, 1 - pin A1 high ----------------' | | +// | |0 - pin A0 low, 1 - pin A0 high ------------------' | +// $FF8609|byte |DMA base and counter (High byte) |R/W +// $FF860B|byte |DMA base and counter (Mid byte) |R/W +// $FF860D|byte |DMA base and counter (Low byte) |R/W +// | |Note: write address from low toward high byte | + +module dma ( + // clocks and system interface + input clk, + input reset, + input [1:0] bus_cycle, + input turbo, + output irq, + + // (this really doesn't belong here ...) + output reg [31:0] ctrl_out, + // horizontal and vertical screen adjustments + output reg [15:0] video_adj, + + // cpu interface + input [15:0] cpu_din, + input cpu_sel, + input [2:0] cpu_addr, + input cpu_uds, + input cpu_lds, + input cpu_rw, + output reg [15:0] cpu_dout, + + // spi interface + input sdi, + input sck, + input ss, + output sdo, + + // additional fdc control signals provided by PSG and OSD + input fdc_wr_prot, + input drv_side, + input [1:0] drv_sel, + // additional acsi control signals provided by OSD + input [7:0] acsi_enable, + + // ram interface for dma engine + output ram_br, + output reg ram_read, + output reg ram_write, + output [22:0] ram_addr, + output [15:0] ram_dout, + input [15:0] ram_din +); + +assign irq = fdc_irq || acsi_irq; + +// filter spi clock. the 8 bit gate delay is ~2.5ns in total +wire [7:0] spi_sck_D = { spi_sck_D[6:0], sck } /* synthesis keep */; +wire spi_sck = (spi_sck && spi_sck_D != 8'h00) || + (!spi_sck && spi_sck_D == 8'hff); + +reg [5:0] cnt; // bit counter (counting spi bits, rolling from 39 to 8) +reg [4:0] bcnt; // payload byte counter +reg [30:0] sbuf; // receive buffer (buffer used to assemble spi bytes/words) +reg [7:0] cmd; // command byte (first byte of spi transmission) + +// word address given bei io controller +reg data_io_addr_strobe; + +// dma sector count and mode registers +reg [7:0] dma_scnt; +reg [15:0] dma_mode; + +// ============= FDC submodule ============ + +// select signal for the fdc controller registers +wire fdc_reg_sel = cpu_sel && !cpu_lds && (cpu_addr == 3'h2) && (dma_mode[4:3] == 2'b00); +wire fdc_irq; +wire [7:0] fdc_status_byte; +wire [7:0] fdc_dout; + +fdc fdc( .clk ( clk ), + .reset ( reset ), + + .irq ( fdc_irq ), + + // external floppy control signals + .drv_sel ( drv_sel ), + .drv_side ( drv_side ), + .wr_prot ( fdc_wr_prot ), + + // signals from/to io controller + .dma_ack ( dma_ack ), + .status_sel ( bcnt-4'd4 ), + .status_byte ( fdc_status_byte ), + + // cpu interfaces, passed trough dma in st + .cpu_sel ( fdc_reg_sel ), + .cpu_addr ( dma_mode[2:1] ), + .cpu_rw ( cpu_rw ), + .cpu_din ( cpu_din[7:0] ), + .cpu_dout ( fdc_dout ) +); + +// ============= ACSI submodule ============ + +// select signal for the acsi controller access (write only, status comes from io controller) +wire acsi_reg_sel = cpu_sel && !cpu_lds && (cpu_addr == 3'h2) && (dma_mode[4:3] == 2'b01); +wire acsi_irq; +wire [7:0] acsi_status_byte; +wire [7:0] acsi_dout; + +acsi acsi(.clk ( clk ), + .reset ( reset ), + + .irq ( acsi_irq ), + + // acsi target enable + .enable ( acsi_enable ), + + // signals from/to io controller + .dma_ack ( dma_ack ), + .dma_nak ( dma_nak ), + .dma_status ( dma_status ), + + .status_sel ( bcnt-4'd9 ), + .status_byte ( acsi_status_byte ), + + // cpu interface, passed through dma in st + .cpu_sel ( acsi_reg_sel ), + .cpu_addr ( dma_mode[2:1] ), + .cpu_rw ( cpu_rw ), + .cpu_din ( cpu_din[7:0] ), + .cpu_dout ( acsi_dout ) +); + +// ============= CPU read interface ============ +always @(cpu_sel, cpu_rw, cpu_addr, dma_mode, dma_addr, dma_scnt) begin + cpu_dout = 16'h0000; + + if(cpu_sel && cpu_rw) begin + + // register $ff8604 + if(cpu_addr == 3'h2) begin + if(dma_mode[4] == 1'b0) begin + // controller access register + if(dma_mode[3] == 1'b0) + cpu_dout = { 8'h00, fdc_dout }; + else + cpu_dout = { 8'h00, acsi_dout }; + end else + cpu_dout = { 8'h00, dma_scnt }; // sector count register + end + + // DMA status register $ff8606 + // bit 0 = 1: DMA_OK, bit 2 = state of FDC DRQ + if(cpu_addr == 3'h3) cpu_dout = { 14'd0, dma_scnt != 0, 1'b1 }; + + // dma address read back at $ff8609-$ff860d + if(cpu_addr == 3'h4) cpu_dout = { 8'h00, dma_addr[22:15] }; + if(cpu_addr == 3'h5) cpu_dout = { 8'h00, dma_addr[14:7] }; + if(cpu_addr == 3'h6) cpu_dout = { 8'h00, dma_addr[6:0], 1'b0 }; + end +end + +// ============= CPU write interface ============ +// flags indicating the cpu is writing something. valid on rising edge +reg cpu_address_write_strobe; +reg cpu_scnt_write_strobe; +reg cpu_mode_write_strobe; +reg cpu_dma_mode_direction_toggle; + +always @(negedge clk) begin + if(reset) begin + cpu_address_write_strobe <= 1'b0; + cpu_scnt_write_strobe <= 1'b0; + cpu_mode_write_strobe <= 1'b0; + cpu_dma_mode_direction_toggle <= 1'b0; + end else begin + cpu_address_write_strobe <= 1'b0; + cpu_scnt_write_strobe <= 1'b0; + cpu_mode_write_strobe <= 1'b0; + cpu_dma_mode_direction_toggle <= 1'b0; + + // cpu writes ... + if(cpu_sel && !cpu_rw && !cpu_lds) begin + + // ... sector count register + if((cpu_addr == 3'h2) && (dma_mode[4] == 1'b1)) + cpu_scnt_write_strobe <= 1'b1; + + // ... dma mode register + if(cpu_addr == 3'h3) begin + dma_mode <= cpu_din; + cpu_mode_write_strobe <= 1'b1; + + // check if cpu toggles direction bit (bit 8) + if(dma_mode[8] != cpu_din[8]) + cpu_dma_mode_direction_toggle <= 1'b1; + end + + // ... dma address + if((cpu_addr == 3'h4) || (cpu_addr == 3'h5) || (cpu_addr == 3'h6)) + // trigger address engine latch + cpu_address_write_strobe <= 1'b1; + end + end +end + + +// SPI data io and strobe +// TODO: these don't belong here +reg [15:0] io_data_in; +reg io_data_in_strobe; // data from IOC ready to be stored in fifo +reg io_data_out_strobe; +reg io_data_out_inc_strobe; + +// ======================== BUS cycle handler =================== + +// latch bus cycle to have it stable at the end of the cycle +// (rising edge of clk8) +reg [1:0] bus_cycle_L; +always @(negedge clk) bus_cycle_L <= bus_cycle; + +// specify which bus cycles to use +wire cycle_advance = (bus_cycle == 2'd0) || (turbo && (bus_cycle == 2'd2)); +wire cycle_advance_L = (bus_cycle_L == 2'd0) || (turbo && (bus_cycle_L == 2'd2)); +wire cycle_io = (bus_cycle == 2'd1) || (turbo && (bus_cycle == 2'd3)); +wire cycle_io_L = (bus_cycle_L == 2'd1) || (turbo && (bus_cycle_L == 2'd3)); + +reg [2:0] fifo_read_cnt; // fifo read transfer word counter +reg [2:0] fifo_write_cnt; // fifo write transfer word counter + +// ======================================================================= +// ============================= DMA FIFO ================================ +// ======================================================================= + +// 32 byte dma fifo (actually a 16 word fifo) +reg [15:0] fifo [15:0]; +reg [3:0] fifo_wptr; // word pointers +reg [3:0] fifo_rptr; +wire [3:0] fifo_ptr_diff = fifo_wptr - fifo_rptr; + +// fifo is ready to be re-filled if it is empty +// -> we don't use this as the fifo is not refilled fast enough in non-turbo +wire fifo_not_full = fifo_ptr_diff < 4'd7; // (turbo?4'd1:4'd2); +// fifo is considered full if 8 words (16 bytes) are present +wire fifo_full = (fifo_wptr - fifo_rptr) > 4'd7; + +// Reset fifo via the dma mode direction bit toggling or when +// IO controller sets address +wire fifo_reset = cpu_dma_mode_direction_toggle || data_io_addr_strobe; + +reg fifo_read_in_progress, fifo_write_in_progress; +assign ram_br = fifo_read_in_progress || fifo_write_in_progress || ioc_br_clk; + +reg ioc_br_clk; +always @(negedge clk) + if(cycle_advance) + ioc_br_clk <= ioc_br; + +// ============= FIFO WRITE ENGINE ================== +// rising edge after ram has been read +reg ram_read_done; +always @(posedge clk) ram_read_done <= ram_read; + +// state machine for DMA ram read access +// this runs on negative clk to generate a proper bus request +// in the middle of the advance cycle + +// start condition for fifo write +wire fifo_write_start = dma_in_progress && dma_direction_out && + fifo_write_cnt == 0 && fifo_not_full; + +always @(negedge clk or posedge fifo_reset) begin + if(fifo_reset == 1'b1) begin + fifo_write_cnt <= 3'd0; + fifo_write_in_progress <= 1'b0; + end else begin + if(cycle_advance) begin + // start dma read engine if 8 more words can be stored + if(fifo_write_start) begin + fifo_write_cnt <= 3'd7; + fifo_write_in_progress <= 1'b1; + end else begin + if(fifo_write_cnt != 0) + fifo_write_cnt <= fifo_write_cnt - 3'd1; + else + fifo_write_in_progress <= 1'b0; + end + end + end +end + +// ram control signals need to be stable over the whole 8 Mhz cycle +always @(posedge clk) + ram_read <= (cycle_advance_L && fifo_write_in_progress)?1'b1:1'b0; + +// Bring word from io controller into local clock domain before +// feeding it into the fifo. This is necessary to prevent race +// conditions on the fifo_full|empty signals when wptr and rptr +// are altered in different clocks +reg io_data_in_strobe_latch; +reg io_data_in_strobe_clk; + +always @(posedge io_data_in_strobe or posedge io_data_in_strobe_clk) begin + // set io_data_in_strobe_latch on io_data_in_strobe and clear + // it on io_data_in_strobe_clk which in turn means that the + // strobe arrived in clk domain + if(io_data_in_strobe_clk) io_data_in_strobe_latch <= 1'b0; + else io_data_in_strobe_latch <= 1'b1; +end + +always @(posedge clk) + io_data_in_strobe_clk <= io_data_in_strobe_latch; + +wire [15:0] fifo_data_in = dma_direction_out?ram_din:io_data_in; +wire fifo_data_in_strobe = dma_direction_out?ram_read_done:io_data_in_strobe_clk; + +// write to fifo on rising edge of fifo_data_in_strobe +always @(posedge fifo_data_in_strobe or posedge fifo_reset) begin + if(fifo_reset == 1'b1) + fifo_wptr <= 4'd0; + else begin + fifo[fifo_wptr] <= fifo_data_in; + fifo_wptr <= fifo_wptr + 4'd1; + end +end + +// ============= FIFO READ ENGINE ================== + +// start condition for fifo read +wire fifo_read_start = dma_in_progress && !dma_direction_out && + fifo_read_cnt == 0 && fifo_full; + +// state machine for DMA ram write access +always @(negedge clk or posedge fifo_reset) begin + if(fifo_reset == 1'b1) begin + // not reading from fifo, not writing into ram + fifo_read_cnt <= 3'd0; + fifo_read_in_progress <= 1'b0; + end else begin + if(cycle_advance) begin + // start dma read engine if 8 more words can be stored + if(fifo_read_start) begin + fifo_read_cnt <= 3'd7; + fifo_read_in_progress <= 1'b1; + end else begin + if(fifo_read_cnt != 0) + fifo_read_cnt <= fifo_read_cnt - 3'd1; + else + fifo_read_in_progress <= 1'b0; + end + end + end +end + +// ram control signals need to be stable over the whole 8 Mhz cycle +always @(posedge clk) + ram_write <= (cycle_advance_L && fifo_read_in_progress)?1'b1:1'b0; + +// a signal half a 8mhz cycle earlier than ram_write to prefetch the data +// from the fifo right before ram write +wire fifo_read_prep = fifo_read_start || (fifo_read_cnt != 0); + +reg ram_write_prep; +always @(negedge clk) + ram_write_prep <= (cycle_advance && fifo_read_prep)?1'b1:1'b0; + +// Bring data out strobe from SPI clock domain into DMAs local clock +// domain to make sure fifo write and read run on the same clock and +// signals derived from the fifo counters are thus glitch free. This +// delays the generation of the fifos "not full" signal slighlty which +// in turn requires reloading the fifo even if it still contains one +// word in non-turbo (2MHz). Waiting for the fifo to become empty +// would not reload the first word from memory fast enough. In turbo +// (4Mhz) mode this is no problem and the first word from memory +// is being read before it has to be ready for SPI transmission +reg io_data_out_inc_strobe_latch; +reg io_data_out_inc_strobe_clk; + +always @(posedge io_data_out_inc_strobe or posedge io_data_out_inc_strobe_clk) begin + if(io_data_out_inc_strobe_clk) io_data_out_inc_strobe_latch <= 1'b0; + else io_data_out_inc_strobe_latch <= 1'b1; +end + +always @(posedge clk) + io_data_out_inc_strobe_clk <= io_data_out_inc_strobe_latch; + +reg [15:0] fifo_data_out; +wire fifo_data_out_strobe = dma_direction_out?io_data_out_strobe:ram_write_prep; +wire fifo_data_out_strobe_clk = dma_direction_out?io_data_out_inc_strobe_clk:ram_write; + +always @(posedge fifo_data_out_strobe) + fifo_data_out <= fifo[fifo_rptr]; + +always @(posedge fifo_data_out_strobe_clk or posedge fifo_reset) begin + if(fifo_reset == 1'b1) fifo_rptr <= 4'd0; + else fifo_rptr <= fifo_rptr + 4'd1; +end + +// use fifo output directly as ram data +assign ram_dout = fifo_data_out; + +// ========================================================================== +// =============================== internal registers ======================= +// ========================================================================== + +// ================================ DMA sector count ======================== +// - register is decremented by one after 512 bytes being transferred +// - cpu can write this register directly +// - io controller can write this via the set_address command + +// CPU write access to even addresses +wire cpu_write = cpu_sel && !cpu_rw && !cpu_lds; + +// Delay the write_strobe a little bit as sector_cnt is included in +// dma_scnt_write_strobe and in turn resets sector_strobe. As a result +// sector_strobe would be a very short spike only. +reg dma_scnt_write_strobe_latch; +reg dma_scnt_write_strobe_clk; + +always @(posedge dma_scnt_write_strobe or + posedge dma_scnt_write_strobe_clk) begin + if(dma_scnt_write_strobe_clk) dma_scnt_write_strobe_latch <= 1'b0; + else dma_scnt_write_strobe_latch <= 1'b1; +end + +always @(negedge clk) + dma_scnt_write_strobe_clk <= dma_scnt_write_strobe_latch; + +// keep track of bytes to decrement sector count register +// after 512 bytes (256 words) +reg [7:0] word_cnt; +reg sector_done; +reg sector_strobe; +reg sector_strobe_prepare; + +always @(negedge clk or posedge dma_scnt_write_strobe_clk) begin + if(dma_scnt_write_strobe_clk) begin + word_cnt <= 8'd0; + sector_strobe_prepare <= 1'b0; + sector_strobe <= 1'b0; + sector_done <= 1'b0; + end else begin + if(cycle_io) begin + sector_strobe_prepare <= 1'b0; + sector_strobe <= 1'b0; + + // wait a little after the last word + if(sector_done) begin + sector_done <= 1'b0; + sector_strobe_prepare <= 1'b1; + // trigger scnt decrement + sector_strobe <= 1'b1; + end + end + + // and ram read or write increases the word counter by one + if(ram_write || ram_read) begin + word_cnt <= word_cnt + 8'd1; + if(word_cnt == 255) begin + sector_done <= 1'b1; + // give multiplexor some time ahead ... + sector_strobe_prepare <= 1'b1; + end + end + end +end + +// cpu and io controller can write the scnt register and it's decremented +// after 512 bytes +wire dma_scnt_write_strobe = + cpu_scnt_write_strobe || data_io_addr_strobe || sector_strobe; +wire fifo_in_progress = fifo_read_in_progress || fifo_write_in_progress; +wire cpu_writes_scnt = cpu_write && (cpu_addr == 3'h2) && (dma_mode[4] == 1'b1); +// sector counter doesn't count below 0 +wire [7:0] dma_scnt_dec = (dma_scnt != 0)?(dma_scnt-8'd1):8'd0; +// multiplex new sector count data +wire [7:0] dma_scnt_next = sector_strobe_prepare?dma_scnt_dec: + cpu_writes_scnt?cpu_din[7:0]:sbuf[30:23]; + +// cpu or io controller set the sector count register +always @(posedge dma_scnt_write_strobe) + dma_scnt <= dma_scnt_next; + +// DMA in progress flag: +// - cpu writing the sector count register starts the DMA engine if +// dma enable bit 6 in mode register is clear +// - io controller setting the address starts the dma engine +// - changing sector count to 0 (cpu, io controller or counter) stops DMA +// - cpu writing toggling dma direction stops dma +reg dma_in_progress; +wire dma_stop = cpu_dma_mode_direction_toggle; + +// dma can be started if sector is not zero and if dma is enabled +// by a zero in bit 6 of dma mode register +wire cpu_starts_dma = cpu_writes_scnt && (cpu_din[7:0] != 0) && !dma_mode[6]; +wire ioc_starts_dma = ioc_writes_addr && (sbuf[30:23] != 0); + +always @(posedge dma_scnt_write_strobe or posedge dma_stop) begin + if(dma_stop) dma_in_progress <= 1'b0; + else dma_in_progress <= cpu_starts_dma || ioc_starts_dma || (sector_strobe && dma_scnt_next != 0); +end + +// ========================== DMA direction flag ============================ +reg dma_direction_out; // == 1 when transferring from fpga to io controller +wire cpu_writes_mode = cpu_write && (cpu_addr == 3'h3); +wire dma_direction_set = data_io_addr_strobe || cpu_dma_mode_direction_toggle; + +// bit 8 == 0 -> dma read -> dma_direction_out, io ctrl address bit 23 = dir +wire dma_direction_out_next = cpu_writes_mode?cpu_din[8]:sbuf[22]; + +// cpu or io controller set the dma direction +always @(posedge dma_direction_set) + dma_direction_out <= dma_direction_out_next; + +// ================================= DMA address ============================ +// address can be changed through three events: +// - cpu writes single address bytes into three registers +// - io controller writes address via spi +// - dma engine runs and address is incremented + +// dma address is stored in three seperate registers as +// otherwise verilator complains about signals having multiple driving blocks +reg [7:0] dma_addr_h; +reg [7:0] dma_addr_m; +reg [6:0] dma_addr_l; +wire [22:0] dma_addr = { dma_addr_h, dma_addr_m, dma_addr_l }; + +reg dma_addr_inc; +always @(posedge clk) + dma_addr_inc <= ram_write || ram_read; + +wire cpu_writes_addr = cpu_write && ((cpu_addr == 6) || (cpu_addr == 5) || (cpu_addr == 4)); +wire ioc_writes_addr = (ss == 0) && (cmd == MIST_SET_ADDRESS); +wire dma_addr_write_strobe = dma_addr_inc || data_io_addr_strobe; + +// address to be set by next write strobe +wire [22:0] dma_addr_next = + cpu_writes_addr?{ cpu_din[7:0], cpu_din[7:0], cpu_din[7:1] }: + ioc_writes_addr?{ sbuf[21:0], sdi }: + (dma_addr + 23'd1); + +// dma address low byte +wire dma_addr_write_strobe_l = dma_addr_write_strobe || (cpu_address_write_strobe && (cpu_addr == 3'h6)); +always @(posedge dma_addr_write_strobe_l) + dma_addr_l <= dma_addr_next[6:0]; + +// dma address mid byte +wire dma_addr_write_strobe_m = dma_addr_write_strobe || (cpu_address_write_strobe && (cpu_addr == 3'h5)); +always @(posedge dma_addr_write_strobe_m) + dma_addr_m <= dma_addr_next[14:7]; + +// dma address hi byte +wire dma_addr_write_strobe_h = dma_addr_write_strobe || (cpu_address_write_strobe && (cpu_addr == 3'h4)); +always @(posedge dma_addr_write_strobe_h) + dma_addr_h <= dma_addr_next[22:15]; + +// dma address is used directly to address the ram +assign ram_addr = dma_addr; + +// dma status interface +reg [7:0] dma_status; // sent with ack, only used by acsi +reg io_dma_ack, io_dma_nak; +reg io_dma_ackD, io_dma_nakD; +reg io_dma_ackD2, io_dma_nakD2; +reg dma_ack, dma_nak; + +// bring ack from io controller into local clock domain +always @(posedge clk) begin + io_dma_ackD <= io_dma_ack; + io_dma_nakD <= io_dma_nak; +end + +always @(negedge clk) begin + io_dma_ackD2 <= io_dma_ackD; + io_dma_nakD2 <= io_dma_nakD; + + dma_ack <= (io_dma_ackD && !io_dma_ackD2); + dma_nak <= (io_dma_nakD && !io_dma_nakD2); +end + +// dma status byte as signalled to the io controller +wire [7:0] dma_io_status = + (bcnt == 0)?dma_addr[22:15]: + (bcnt == 1)?dma_addr[14:7]: + (bcnt == 2)?{ dma_addr[6:0], dma_direction_out }: + (bcnt == 3)?dma_scnt: + // 5 bytes FDC status + ((bcnt >= 4)&&(bcnt <= 8))?fdc_status_byte: + // 7 bytes ACSI status + ((bcnt >= 9)&&(bcnt <= 15))?acsi_status_byte: + // DMA debug signals + (bcnt == 16)?8'ha5: + (bcnt == 17)?{ fifo_rptr, fifo_wptr}: + (bcnt == 18)?{ 6'd0, ioc_br_clk, dma_in_progress }: + (bcnt == 19)?dma_status: + 8'h00; + +// ==================================================================== +// ===================== SPI client to IO controller ================== +// ==================================================================== + +// the following must match the codes in tos.h +localparam MIST_SET_ADDRESS = 8'h01; +localparam MIST_WRITE_MEMORY = 8'h02; +localparam MIST_READ_MEMORY = 8'h03; +localparam MIST_SET_CONTROL = 8'h04; +localparam MIST_GET_DMASTATE = 8'h05; // reads state of dma and floppy controller +localparam MIST_ACK_DMA = 8'h06; // acknowledge a dma command +localparam MIST_SET_VADJ = 8'h09; +localparam MIST_NAK_DMA = 8'h0a; // reject a dma command + +// ===================== SPI transmitter ================== + +reg [3:0] sdo_bit; +always @(negedge sck) + sdo_bit <= 4'd7 - cnt[3:0]; + +wire sdo_fifo = fifo_data_out[sdo_bit]; +wire sdo_dmastate = dma_io_status[sdo_bit[2:0]]; + +assign sdo = (cmd==MIST_READ_MEMORY)?sdo_fifo:sdo_dmastate; + +// ===================== SPI receiver ================== + +reg ioc_br = 1'b0; + +always@(posedge spi_sck, posedge ss) begin + if(ss == 1'b1) begin + cmd <= 8'd0; + cnt <= 6'd0; + bcnt <= 4'd0; + io_dma_ack <= 1'b0; + io_dma_nak <= 1'b0; + io_data_in_strobe <= 1'b0; + io_data_out_strobe <= 1'b0; + io_data_out_inc_strobe <= 1'b0; + data_io_addr_strobe <= 1'b0; + end else begin + io_dma_ack <= 1'b0; + io_dma_nak <= 1'b0; + io_data_in_strobe <= 1'b0; + io_data_out_strobe <= 1'b0; + io_data_out_inc_strobe <= 1'b0; + data_io_addr_strobe <= 1'b0; + + // shift bits in. stop shifting after payload + if(cmd == MIST_SET_ADDRESS) begin + // set address is 8+32 bits + if(cnt < 39) + sbuf <= { sbuf[29:0], sdi}; + end else if(cmd == MIST_WRITE_MEMORY) begin + if((cnt != 23) && (cnt != 39)) + sbuf <= { sbuf[29:0], sdi}; + end else + sbuf <= { sbuf[29:0], sdi}; + + // 0:7 is command, 8:15 and 16:23 is payload bytes + if(cnt < 39) + cnt <= cnt + 6'd1; + else + cnt <= 6'd8; + + // count payload bytes + if((cnt == 15) || (cnt == 23) || (cnt == 31) || (cnt == 39)) + bcnt <= bcnt + 4'd1; + + if(cnt == 5'd7) begin + cmd <= {sbuf[6:0], sdi}; + + if({sbuf[6:0], sdi } == 8'h07) + ioc_br <= 1'b1; + + if({sbuf[6:0], sdi } == 8'h08) + ioc_br <= 1'b0; + + // send nak + if({sbuf[6:0], sdi } == MIST_NAK_DMA) + io_dma_nak <= 1'b1; + + // read first byte from fifo once the read command is recognized + if({sbuf[6:0], sdi } == MIST_READ_MEMORY) + io_data_out_strobe <= 1'b1; + end + + // handle "payload" + + // set address + if((cmd == MIST_SET_ADDRESS) && (cnt == 39)) + data_io_addr_strobe <= 1'b1; + + // read ram + if(cmd == MIST_READ_MEMORY) begin + // read word from fifo + if((cnt == 23)||(cnt == 39)) + io_data_out_strobe <= 1'b1; + + // increment fifo read pointer + if((cnt == 8)||(cnt == 24)) + io_data_out_inc_strobe <= 1'b1; + end + + // write ram + if(cmd == MIST_WRITE_MEMORY) begin + // received one word, store it in fifo + if((cnt == 23)||(cnt == 39)) begin + io_data_in <= { sbuf[14:0], sdi }; + io_data_in_strobe <= 1'b1; + end + end + + // dma_ack + if((cmd == MIST_ACK_DMA) && (cnt == 15)) begin + io_dma_ack <= 1'b1; + dma_status <= { sbuf[6:0], sdi }; + end + + // set control register + if((cmd == MIST_SET_CONTROL) && (cnt == 39)) + ctrl_out <= { sbuf, sdi }; + + // set video offsets + if((cmd == MIST_SET_VADJ) && (cnt == 5'd23)) + video_adj <= { sbuf[14:0], sdi }; + end +end + +endmodule diff --git a/tests/verilator/dma/dma_tb.cpp b/tests/verilator/dma/dma_tb.cpp new file mode 100644 index 0000000..3f844e0 --- /dev/null +++ b/tests/verilator/dma/dma_tb.cpp @@ -0,0 +1,731 @@ +// TODO: include real tos.c + +#include "Vdma.h" +#include "verilated.h" +#include "verilated_vcd_c.h" + +Vdma* top = NULL; +VerilatedVcdC* tfp = NULL; + +double time_ns = 0; + +#define MEMBASE 0xfc0000 +#define MEMSIZE 256*1024 +unsigned char mem[MEMSIZE]; + +#define MHZ2NS(a) (1000000000.0/(a)) +#define CLK (8000000.0) + +void hexdump(void *data, int size) { + int i, b2c, n=0; + char *ptr = (char*)data; + + if(!size) return; + + while(size>0) { + printf(" %04x: ", n); + b2c = (size>16)?16:size; + for(i=0;ieval(); + tfp->dump(time_ns); + + // eval on negedge of clk + if(!top->clk && last_clk) { + + // check if someone wants to read + if(top->ram_read) { + unsigned short data = 0; + unsigned long addr = top->ram_addr<<1; + unsigned char *ptr = mem+addr-MEMBASE; + + if((addr >= MEMBASE) && (addr < MEMBASE+MEMSIZE)) + data = 256*ptr[0] + ptr[1]; + else + printf("Reading outside range: %lx\n", addr); + + // printf(" RAM_READ(%lx)=%x\n", addr, data); + top->ram_din = data; + } + + // check if someone wants to write + if(top->ram_write) { + unsigned long addr = top->ram_addr<<1; + unsigned short data = + ((top->ram_dout & 0xff)<<8) | ((top->ram_dout & 0xff00)>>8); + + // printf(" RAM_WRITE(%lx, %x)\n", addr, top->ram_dout); + + if((addr >= MEMBASE) && (addr < MEMBASE+MEMSIZE)) + *(unsigned short*)(mem+addr-MEMBASE) = data; + else + printf("Writing outside range: %lx\n", addr); + } + } + last_clk = top->clk; +} + +// advance time and create valid 8 Mhz clock and signals +// derived from it +void wait_ns(double n) { + static int clk_time = 0; + + eval(); + + // check if next clk event is within waiting period + while(clk_time <= n) { + time_ns += clk_time; // advance time to next clk event + n -= clk_time; // reduce remainung waiting time + + // process change on clk + top->clk = !top->clk; + + // cpu reads on falling clock edge + if((!top->clk) && (top->bus_cycle == 0)) { + // read access in progress? + if(top->cpu_sel && top->cpu_rw) { + cpu_read_data = top->cpu_dout; + cpu_read_addr = 0; + } + } + + if(top->clk) { + // do everything that's supposed to happen on the rising + // edge of clk + top->bus_cycle = (top->bus_cycle + 1)&3; + top->cpu_sel = 0; + + if(top->bus_cycle == 0) { + if(cpu_read_addr) { + // perform cpu read access + top->cpu_sel = (cpu_read_addr & ~0xf) == 0xff8600; + top->cpu_addr = (cpu_read_addr & 0x0f)>>1; + top->cpu_rw = 1; + top->cpu_uds = top->cpu_lds = 0; + } + + // perform cpu write access + if(cpu_write_addr) { + top->cpu_sel = (cpu_write_addr & ~0xf) == 0xff8600; + top->cpu_addr = (cpu_write_addr & 0x0f)>>1; + top->cpu_rw = 0; + top->cpu_din = cpu_write_data; + top->cpu_uds = top->cpu_lds = 0; + cpu_write_addr = 0; + } + } + } + + eval(); + + clk_time = MHZ2NS(CLK)/2; // next clk change in 62.5ns + } + + // next event is when done waiting + time_ns += n; // advance time + clk_time -= n; +} + +void wait_us(double n) { + wait_ns(n * 1000.0); +} + +void wait_ms(double n) { + wait_us(n * 1000.0); +} + +void cpu_write(unsigned long addr, unsigned short data) { + cpu_write_addr = addr; + cpu_write_data = data; + wait_us(1); // wait two 2MHz system cycles +} + +unsigned short cpu_read(unsigned long addr) { + cpu_read_addr = addr; + wait_us(1); // wait two 2MHz system cycles + return cpu_read_data; +} + +#define SPI_CLK 24000000.0 +#define SPI_CLK_NS MHZ2NS(SPI_CLK) + +// send a byte over spi +unsigned char SPI(unsigned char byte) { + unsigned char bit; + unsigned char retval = 0; + + // spi at 24Mhz: complete cycle = 41.6666ns + + // data has to be stable on pos edge of sck + for(bit=0;bit<8;bit++) { + wait_ns(SPI_CLK_NS/4); + top->sck = 0; + wait_ns(SPI_CLK_NS/4); + top->sdi = (byte & (0x80>>bit))?1:0; + retval = (retval << 1) | (top->sdo?1:0); + wait_ns(SPI_CLK_NS/4); + top->sck = 1; + wait_ns(SPI_CLK_NS/4); + } + + return retval; +} + +#define SPI_WRITE(a) SPI(a) + +void EnableFpga(void) { + wait_ns(100); + top->ss = 0; + wait_ns(100); +} + +void DisableFpga(void) { + wait_ns(200); + top->ss = 1; + wait_ns(10); +} + +// ------- routines takes from firmware tos.c ---------------- +#define MIST_SET_ADDRESS 0x01 +#define MIST_WRITE_MEMORY 0x02 +#define MIST_READ_MEMORY 0x03 +#define MIST_SET_CONTROL 0x04 +#define MIST_GET_DMASTATE 0x05 // reads state of dma and floppy controller +#define MIST_ACK_DMA 0x06 // acknowledge a dma command +#define MIST_SET_VADJ 0x09 +#define MIST_NAK_DMA 0x0a // reject a dma command + +static void mist_memory_set_address(unsigned long a, unsigned char s, char rw) { + a |= rw?0x1000000:0; + a >>= 1; + + EnableFpga(); + SPI(MIST_SET_ADDRESS); + SPI(s); + SPI((a >> 16) & 0xff); + SPI((a >> 8) & 0xff); + SPI((a >> 0) & 0xff); + DisableFpga(); +} + +static void mist_memory_write(char *data, unsigned long words) { + + EnableFpga(); + SPI(MIST_WRITE_MEMORY); + + while(words--) { + SPI_WRITE(*data++); + SPI_WRITE(*data++); + } + + DisableFpga(); +} + +static void mist_memory_read(char *data, unsigned long words) { + + EnableFpga(); + SPI(MIST_READ_MEMORY); + + // transmitted bytes must be multiple of 2 (-> words) + while(words--) { + *data++ = SPI(0); + *data++ = SPI(0); + // printf("SPI RX: %02x %02x\n", *(data-2), *(data-1)); + } + + DisableFpga(); +} + +void tos_set_video_adjust(unsigned char a, unsigned char b) { + EnableFpga(); + SPI(MIST_SET_VADJ); + SPI(a); + SPI(b); + DisableFpga(); +} + +static void mist_set_control(unsigned long ctrl) { + EnableFpga(); + SPI(MIST_SET_CONTROL); + SPI((ctrl >> 24) & 0xff); + SPI((ctrl >> 16) & 0xff); + SPI((ctrl >> 8) & 0xff); + SPI((ctrl >> 0) & 0xff); + DisableFpga(); +} + +static void mist_get_dmastate(unsigned char *buffer) { + int i; + + EnableFpga(); + SPI(MIST_GET_DMASTATE); + for(i=0;i<16;i++) + buffer[i] = SPI(0); + DisableFpga(); + + // printf(" IO controllers view on DMA state:\n"); + // hexdump(buffer, 16); + + // check if acsi is busy + if(buffer[15] & 1) + printf("ACSI busy flag set\n"); + // handle_acsi(buffer); + + // check if fdc is busy + if(buffer[8] & 1) { + printf("FDC busy flag set\n"); + printf("FDC CMD = %x\n", buffer[4]); + printf("FDC TRK = %x\n", buffer[5]); + printf("FDC SEC = %x\n", buffer[6]); + printf("FDC DAT = %x\n", buffer[7]); + printf("FDC FLG = %x\n", buffer[8]); + + // handle_fdc(buffer); + } +} + +static void spi_noise() { + int i, cnt = random()&15; + for(i=0;iram_addr<<1) != a) { + printf("ERROR: DMA address is %x, should be %lx\n", + top->ram_addr<<1, a); + tfp->close(); + exit(0); + } + + if(top->v__DOT__dma_scnt != s) { + printf(" ERROR: Sector count is %d, should be %d\n", + top->v__DOT__dma_scnt, s); + tfp->close(); + exit(0); + } + + // check that direction matches + if(top->v__DOT__dma_direction_out != rw) { + printf(" ERROR: Direction is incorrect, is %d, should be %d\n", + top->v__DOT__dma_direction_out, rw); + tfp->close(); + exit(0); + } + + // cpu only works if system is not in reset + if(!top->reset) { + unsigned long cpu_dma_addr = + (cpu_read(0xFF8609)<<16) | (cpu_read(0xFF860B)<<8) | cpu_read(0xFF860D); + + // check that dma address visible to cpu matches + if(cpu_dma_addr != a) { + printf("ERROR: cpu visible DMA address is %lx, should be %lx\n", + cpu_dma_addr, a); + tfp->close(); + exit(0); + } + + // enable access to sector count register + cpu_write(0xFF8606, 0x10); + + unsigned char cpu_dma_scnt = cpu_read(0xFF8604); + if(cpu_dma_scnt != s) { + printf(" ERROR: CPU visible sector count is %d, should be %d\n", + cpu_dma_scnt, s); + tfp->close(); + exit(0); + } + } + + // and check address and sector count encoded in dma state + mist_get_dmastate(buffer); + + // printf(" FIFO: r=%d, w=%d\n", (buffer[10]>>4)&0x0f, buffer[10]&0x0f); + + unsigned long ioc_dma_addr = + (buffer[0] << 16) + (buffer[1] << 8) + (buffer[2]&0xfe); + + if(ioc_dma_addr != a) { + printf("ERROR: io controller visible DMA address is %lx, should be %lx\n", + ioc_dma_addr, a); + tfp->close(); + exit(0); + } + + if(buffer[3] != s) { + printf(" ERROR: IO controller visible sector count is %d, should be %d\n", + buffer[3], s); + tfp->close(); + exit(0); + } + + printf(" dir %s, scnt %d, addr $%lx ok. CPU %s\n", + rw?"out":"in", s, a, top->reset?"ignored":"ok"); +} + +void port_test() { + printf("== IO controller port test ==\n"); + + // test some config/setup routines + tos_set_video_adjust(0x12, 0x34); + if(top->video_adj != 0x1234) { + printf("ERROR: setting vadj failed\n"); + tfp->close(); + exit(1); + } else + printf(" Video adjustment ok\n"); + + mist_set_control(0x12345678); + if(top->ctrl_out != 0x12345678) { + printf("ERROR: setting control failed\n"); + tfp->close(); + exit(1); + } else + printf(" Control register write ok\n"); +} + +// $FF8606|word |DMA mode/status BIT 8 7 6 . 4 3 2 1 .|W +// | |0 - read FDC/HDC,1 - write ---------' | | | | | | | | +// | |0 - HDC access,1 - FDC access --------' | | | | | | | +// | |0 - DMA on,1 - no DMA ------------------' | | | | | | +// | |Reserved ---------------------------------' | | | | | +// | |0 - FDC reg,1 - sector count reg -----------' | | | | +// | |0 - FDC access,1 - HDC access ----------------' | | | +// | |0 - pin A1 low, 1 - pin A1 high ----------------' | | +// | |0 - pin A0 low, 1 - pin A0 high ------------------' | + +void dma_mode_dump() { + printf(">> DMA mode = %x\n", top->v__DOT__dma_mode); + + if(!(top->v__DOT__dma_mode & 0x100)) + printf(" - DMA read (%s)\n", top->v__DOT__dma_direction_out?"write":"read"); + else + printf(" - DMA write (%s)\n", top->v__DOT__dma_direction_out?"write":"read"); + if(!(top->v__DOT__dma_mode & 0x80)) printf(" - ACSI DMA\n"); else printf(" - FDC DMA\n"); + if(!(top->v__DOT__dma_mode & 0x40)) printf(" - DMA on\n"); else printf(" - DMA off\n"); + if(!(top->v__DOT__dma_mode & 0x10)) printf(" - FDC/ACSI reg\n"); else printf(" - Sector count\n"); + if(!(top->v__DOT__dma_mode & 0x08)) printf(" - FDC access\n"); else printf(" - ACSI access\n"); + printf(" - A1/A0 = %d\n", (top->v__DOT__dma_mode >> 1)&3); + + // fdc registers + printf("FDC CMD: %x\n", top->v__DOT__fdc__DOT__cmd); + printf("FDC TRK: %x\n", top->v__DOT__fdc__DOT__track); + printf("FDC SEC: %x\n", top->v__DOT__fdc__DOT__sector); + printf("FDC DAT: %x\n", top->v__DOT__fdc__DOT__data); +} + +void dma_cpu_test() { + unsigned char buffer[16]; + + // ----- now start a dma transfer from the cpu interface ----- + + printf("== Core DMA write test ==\n"); + + dma_mode_dump(); + + // set dma address + cpu_write(0xFF8609, 0xfc); + cpu_write(0xFF860B, 0x00); + cpu_write(0xFF860D, 0x00); + + // enable access to sector count register + cpu_write(0xFF8606, 0x10); + + dma_mode_dump(); + + // write 1 to sector counter register (starts dma on write to io controller) + cpu_write(0xFF8604, 0x01); + + dma_mode_dump(); + + // dma transfer itself is done by the io controller + +#define dmal 0xff860d +#define dmam 0xff860b +#define dmah 0xff8609 + +#define fdcc 0xff8604 +#define dmac 0xff8606 + + mist_get_dmastate(buffer); + + // a2 = dmac, a3 = fdcc + cpu_write(dmac, 0x82); // *Track register + dma_mode_dump(); + + printf("current track = %d\n", cpu_read(fdcc)); //* Current track + cpu_write(fdcc, 0x01); + printf("current track = %d\n", cpu_read(fdcc)); //* Current track + + cpu_write(dmac, 0x86); // *Data register + cpu_write(fdcc, 0x01); + + mist_get_dmastate(buffer); + + printf("181:\n"); + cpu_write(dmac, 0x181); // *Data register + + dma_mode_dump(); + + // move.w #$13,(a3) *Seek command, steprate 3ms + // bsr comexd +} + +void io_write_test(char *data, int size, int chunk_size) { + printf("== IO controller DMA %d bytes %s write test ==\n", + size, chunk_size?"chunky":"burst"); + + if(chunk_size) printf(" Chunk size = %d\n", chunk_size); + + unsigned char scnt = (size+511)/512; + printf(" Sector count will be %d\n", scnt); + + // destination address can be set externally + mist_memory_set_address(MEMBASE, scnt, 0); + dma_address_verify(MEMBASE, scnt, 0); + + // data is transferred in 16 bytes chunks + unsigned long bytes = size & ~0x0f; + + // initiate dma transfer + printf(" Sending %d bytes via dma, expecting %lu to arrive ...\n", + size, bytes); + + if(chunk_size > 0) { + int sent = 0; + char *p = data; + int s = size; + while(s) { + // printf("chunk\n"); + int cs = (s>=chunk_size)?chunk_size:s; + mist_memory_write(p, cs/2); + p += chunk_size; + s -= cs; + sent += chunk_size; + // printf("after: sent=%d, rem=%d\n", sent, s); + + if(s) + dma_address_verify(MEMBASE+(sent&~15), (s+511)/512, 0); + } + } else + mist_memory_write(data, size/2); + + wait_us(5); + + // check that data has arrived in memory + if(memcmp(data, mem, bytes) != 0) { + int i=0; + while(data[i] == (char)(mem[i])) i++; + + printf("ERROR: dma verify failed at index %d\n", i); + + hexdump(data, 32); + hexdump(mem, 32); + + tfp->close(); + exit(0); + } else + printf(" %ld bytes successfully verified\n", bytes); + + // if transferred bytes is not a multiple of 512, then scnt will not reach 0 + dma_address_verify(MEMBASE+bytes, (bytes&0x1ff)?1:0, 0); + + // printf(" RAM contents now:\n"); + // hexdump(mem, 48); +} + +void io_read_test(char *data, int size, int chunk_size) { + unsigned char test_rx[size]; + + // inject test data into memory + memcpy(mem, data, size); + + // ----- read data back via DMA interface ----- + printf("== IO controller DMA %d bytes %s read test ==\n", + size, chunk_size?"chunky":"burst"); + + unsigned char scnt = (size+511)/512; + printf(" Sector count will be %d\n", scnt); + + // data is transferred in 16 bytes chunks + // unsigned long bytes = (size+16) & ~0x0f; + unsigned long bytes = (size+31) & ~0x0f; + // but never more than scnt allows + if(bytes > 512*scnt) bytes = 512*scnt; + + // re-set destination address as it has advanced during + // the DMA transfer + // address bit 24 == 1 -> dma read + mist_memory_set_address(MEMBASE, scnt, 1); + + // initiate dma transfer + printf(" Receiving %d bytes via dma, expecting %d bytes to be read ...\n", + size, bytes); + + // A whole turbo dma read of 16 bytes takes 8*250ns = 2us. + // We can start earlier since only one word has to be arrived + // in the fifo for us to read. + wait_us(1); + + if(chunk_size > 0) { + spi_noise(); + + int mb = MEMBASE; + char *p = (char*)test_rx; + int s = size; + while(s) { + int cs = (s>=chunk_size)?chunk_size:s; + mist_memory_read(p, cs/2); + p += chunk_size; + s -= cs; + mb+=512; + dma_address_verify(mb+(s?16:0), s/512, 1); + } + } else + mist_memory_read((char*)test_rx, size/2); + + // hexdump(test_rx, sizeof(test_rx)); + + // check that data has arrived in buffer + if(memcmp(test_rx, mem, size) != 0) { + int i=0; + while(test_rx[i] == mem[i]) i++; + + printf("ERROR: dma verify failed at index %d\n", i); + + hexdump(mem, 48); + hexdump(test_rx, 48); + + tfp->close(); + exit(0); + } else + printf(" %ld bytes successfully verified\n", size); + + // the DMA will reload after this, so wait some time for it to get + // into a stable (non-empty) state again + wait_us(5); + + dma_address_verify(MEMBASE+bytes, (bytes&0x1ff)?1:0, 1); +} + +int main(int argc, char **argv, char **env) { + Verilated::commandArgs(argc, argv); + // init top verilog instance + top = new Vdma; + + srandom(time(NULL)); + + // init trace dump + Verilated::traceEverOn(true); + tfp = new VerilatedVcdC; + top->trace (tfp, 99); + tfp->open ("dma.vcd"); + + // initialize system inputs + top->clk = 1; + top->reset = 1; + + // enabling the turbo doesn't make much sense as the dma in non-turbo + // at 2Mcycles/sec can transfer 4MBytes/sec is always faster than the + // SPI at 24Mhz doing 3MBytes/sec + top->turbo = 0; + + // init cpu interface + top->cpu_sel <= 0; + + // init spi + top->sdi = 1; + top->ss = 1; + top->sck = 1; + + // floppy + top->drv_sel = 3; // no drive delected + top->drv_side = 0; + top->fdc_wr_prot = 0; + + wait_us(1); + + port_test(); + + unsigned char test[2048]; + int i; +#if 1 + for(i=0;iv__DOT__dma_in_progress) { + printf("ERROR: DMA is expected to still be active after incomplete transfer\n"); + tfp->close(); + exit(1); + } + + // stop uncompleted DMA + mist_memory_set_address(0,0,0); + + if(top->v__DOT__dma_in_progress) { + printf("ERROR: DMA is expected to be stopped now\n"); + tfp->close(); + exit(1); + } + + // multiple of 16 and multiple of 512 + io_write_test((char*)test, sizeof(test), 0); + io_read_test((char*)test, sizeof(test), 0); + + // leave reset + top->reset = 0; + + // multiple of 16 and multiple of 512 in chunks + io_write_test((char*)test, sizeof(test), 512); + io_read_test((char*)test, sizeof(test), 512); +#endif + + top->reset = 0; + wait_ns(100); + + // dma_cpu_test(); + + tfp->close(); + + exit(0); + } + diff --git a/tests/verilator/dma/fdc.v b/tests/verilator/dma/fdc.v new file mode 100644 index 0000000..b77a843 --- /dev/null +++ b/tests/verilator/dma/fdc.v @@ -0,0 +1,272 @@ +// fdc.v +// +// Atari ST floppy implementation for the MIST baord +// http://code.google.com/p/mist-board/ +// +// Copyright (c) 2014 Till Harbaum +// +// This source file 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. +// +// This source file 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 . +// + +module fdc ( + // clocks and system interface + input clk, + input reset, + + // write protection of currently selected floppy + input [1:0] drv_sel, + input drv_side, + input wr_prot, + + input dma_ack, + input [2:0] status_sel, + output [7:0] status_byte, + + // cpu interface + input [1:0] cpu_addr, + input cpu_sel, + input cpu_rw, + input [7:0] cpu_din, + output reg [7:0] cpu_dout, + + output reg irq +); + +// fdc_busy is a counter. counts down from 2 to 0. stays at 3 since that +// means that the fdc is waiting for the arm io controller +localparam STATE_IDLE = 2'd0; +localparam STATE_IRQ = 2'd1; +localparam STATE_INT_WAIT = 2'd2; +localparam STATE_IO_WAIT = 2'd3; + +reg [1:0] state; // fdc busy state + +// the fdc registers +reg [7:0] cmd; // write only +reg [7:0] track; +reg [7:0] sector; +reg [7:0] data; + +// fdc status as reported to the io controller +assign status_byte = + (status_sel == 0)?cmd: + (status_sel == 1)?track: + (status_sel == 2)?sector: + (status_sel == 3)?data: + (status_sel == 4)?{ 4'b0000, drv_sel, drv_side, state == STATE_IO_WAIT }: + 8'h00; + +reg step_dir; + +reg [31:0] delay; + +wire cmd_type_1 = (cmd[7] == 1'b0); +wire cmd_type_2 = (cmd[7:6] == 2'b10); + +// ---------------- floppy motor simulation ------------- + +// timer to simulate motor-on. This runs for x/8000000 seconds after each command +reg motor_start; +reg [31:0] motor_on_counter; +wire motor_on = (motor_on_counter != 0); + +// motor_on_counter > 16000000 means the motor is spinning up +wire motor_spin_up_done = motor_on && (motor_on_counter <= 16000000); + +always @(posedge clk or posedge motor_start) begin + if(motor_start) + // motor runs for 2 seconds if it was already on. it rus for one + // more second if if wasn't on yet (spin up) + motor_on_counter <= motor_on?32'd16000000:32'd24000000; + else begin + // let "motor" run + if(motor_on_counter != 0) + motor_on_counter <= motor_on_counter - 32'd1; + end +end + +// -------------- index pulse generation ---------------- + +// floppy rotates at 300rpm = 5rps -> generate 5 index pulses per second +wire index_pulse = index_pulse_cnt > 32'd1500000; // 1/16 rotation +reg [31:0] index_pulse_cnt; +always @(posedge clk) begin + if(!motor_on) + index_pulse_cnt <= 32'd0; + else begin + if(index_pulse_cnt != 0) + index_pulse_cnt <= index_pulse_cnt - 32'd1; + else + index_pulse_cnt <= 32'd1600000; // 8000000/5 + end +end + +// status byte returned by the fdc when reading register 0 +wire [7:0] status = { + motor_on, + wr_prot, + cmd_type_1?motor_spin_up_done:1'b0, + 2'b00 /* track not found/crc err */, + cmd_type_1?(track == 0):1'b0, + cmd_type_1?index_pulse:(state!=STATE_IDLE), + state != STATE_IDLE +}; + +// CPU register read +always @(cpu_sel, cpu_addr, cpu_rw) begin + cpu_dout = 8'h00; + + if(cpu_sel && cpu_rw) begin + case(cpu_addr) + 0: cpu_dout = status; + 1: cpu_dout = track; + 2: cpu_dout = sector; + 3: cpu_dout = data; + endcase + end +end + +// CPU register write +always @(negedge clk or posedge reset) begin + if(reset) begin + // clear internal registers + cmd <= 8'h00; + track <= 8'h00; + sector <= 8'h00; + data <= 8'h00; + + // reset state machines and counters + state <= STATE_IDLE; + irq <= 1'b0; + motor_start <= 1'b0; + delay <= 32'd0; + + end else begin + motor_start <= 1'b0; + + // DMA transfer has been ack'd by io controller + if(dma_ack) begin + // fdc waiting for io controller + if(state == STATE_IO_WAIT) + state <= STATE_IRQ; // jump to end of busy phase + end + + // fdc may be waiting internally (e.g. for step completion) + if(state == STATE_INT_WAIT) begin + // count down and go into irq state if done + if(delay != 0) + delay <= delay - 32'd1; + else + state <= STATE_IRQ; + end + + // fdc is ending busy phase + if(state == STATE_IRQ) begin + irq <= 1'b1; + state <= STATE_IDLE; + end + + // cpu is reading status register -> clear fdc irq + if(cpu_sel && cpu_rw && (cpu_addr == 0)) + irq <= 1'b0; + + if(cpu_sel && !cpu_rw) begin + // fdc register write + if(cpu_addr == 0) begin // command register + cmd <= cpu_din; + state <= STATE_INT_WAIT; + delay <= 31'd0; + irq <= 1'b0; + + // all TYPE I and TYPE II commands start the motor + if((cpu_din[7] == 1'b0) || (cpu_din[7:6] == 2'b10)) + motor_start <= 1'b1; + + // ------------- TYPE I commands ------------- + if(cpu_din[7:4] == 4'b0000) begin // RESTORE + track <= 8'd0; + delay <= 31'd2000000; // 250ms delay + end + + if(cpu_din[7:4] == 4'b0001) begin // SEEK + track <= data; + delay <= 31'd200000; // 25ms delay + end + + if(cpu_din[7:3] == 3'b001) begin // STEP + delay <= 31'd20000; // 2.5ms delay + if(cpu_din[4]) // update flag + track <= (step_dir == 1)?(track + 8'd1):(track - 8'd1); + end + + if(cpu_din[7:5] == 3'b010) begin // STEP-IN + delay <= 31'd20000; // 2.5ms delay + step_dir <= 1'b1; + if(cpu_din[4]) // update flag + track <= track + 8'd1; + end + + if(cpu_din[7:5] == 3'b011) begin // STEP-OUT + delay <= 31'd20000; // 2.5ms delay + step_dir <= 1'b0; + if(cpu_din[4]) // update flag + track <= track - 8'd1; + end + + // ------------- TYPE II commands ------------- + if(cpu_din[7:5] == 3'b100) begin // read sector + state <= STATE_IO_WAIT; + end + + if(cpu_din[7:5] == 3'b101) // write sector + if(!wr_prot) + state <= STATE_IO_WAIT; + + // ------------- TYPE III commands ------------ + + if(cpu_din[7:4] == 4'b1100) // read address + state <= STATE_IO_WAIT; + + if(cpu_din[7:4] == 4'b1110) // read track + state <= STATE_IO_WAIT; + + if(cpu_din[7:4] == 4'b1111) // write track + if(!wr_prot) + state <= STATE_IO_WAIT; + + // ------------- TYPE IV commands ------------- + if(cpu_din[7:4] == 4'b1101) begin // force intrerupt + if(cpu_din[3:0] == 4'b0000) + state <= STATE_IDLE; // immediately + else + state <= STATE_IRQ; // with irq + end + end // if (cpu_addr == 0) + + if(cpu_addr == 1) // track register + track <= cpu_din; + + if(cpu_addr == 2) // sector register + sector <= cpu_din; + + if(cpu_addr == 3) // data register + data <= cpu_din; + + end // if (cpu_sel && !cpu_rw) + end // else: !if(reset) +end // always @ (negedge clk or posedge reset) + +endmodule + +