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:
22
tests/verilator/dma/Makefile
Normal file
22
tests/verilator/dma/Makefile
Normal 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
120
tests/verilator/dma/acsi.v
Normal 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
113
tests/verilator/dma/dma.sav
Normal 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
776
tests/verilator/dma/dma.v
Normal 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
|
||||
731
tests/verilator/dma/dma_tb.cpp
Normal file
731
tests/verilator/dma/dma_tb.cpp
Normal 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
272
tests/verilator/dma/fdc.v
Normal 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user