1
0
mirror of https://github.com/mist-devel/mist-board.git synced 2026-01-27 04:11:51 +00:00

Atari ST simulation

This commit is contained in:
harbaum
2015-01-22 19:21:13 +00:00
parent 806f20e92a
commit a910263a8f
6 changed files with 2034 additions and 0 deletions

View File

@@ -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 &

120
tests/verilator/dma/acsi.v Normal file
View File

@@ -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 <till@harbaum.org>
//
// 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 <http://www.gnu.org/licenses/>.
//
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

113
tests/verilator/dma/dma.sav Normal file
View File

@@ -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

776
tests/verilator/dma/dma.v Normal file
View File

@@ -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 <till@harbaum.org>
//
// 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 <http://www.gnu.org/licenses/>.
//
// 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

View File

@@ -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;i<b2c;i++) printf("%02x ", 0xff&ptr[i]);
printf(" ");
for(i=0;i<(16-b2c);i++) printf(" ");
for(i=0;i<b2c;i++) printf("%c", isprint(ptr[i])?ptr[i]:'.');
printf("\n");
ptr += b2c;
size -= b2c;
n += b2c;
}
}
unsigned long cpu_read_addr = 0;
unsigned short cpu_read_data;
unsigned long cpu_write_addr = 0;
unsigned short cpu_write_data;
void eval(void) {
static int last_clk = 0;
// evaluate recent changes
top->eval();
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;i<cnt;i++)
SPI(random());
}
void dma_address_verify(unsigned long a, unsigned char s, char rw) {
unsigned char buffer[16];
wait_us(5);
// check that address has advanced correctly
if((top->ram_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;i<sizeof(test);i++) test[i] = random();
#else
for(i=0;i<sizeof(test);i++) test[i] = i;
#endif
// small chunks (as e.q. inquiry would do)
io_write_test((char*)test, 100, 20);
#if 0
// multiple of 16 but less than 512
io_write_test((char*)test, 32, 0);
io_read_test((char*)test, 32, 0);
// not multiple of 16
io_write_test((char*)test, 40, 0);
io_read_test((char*)test, 40, 0);
if(!top->v__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);
}

272
tests/verilator/dma/fdc.v Normal file
View File

@@ -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 <till@harbaum.org>
//
// 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 <http://www.gnu.org/licenses/>.
//
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