zxuno-git/cores/Spectrum/common/dma.v

309 lines
10 KiB
Verilog
Raw Blame History

`timescale 1ns / 1ps
`default_nettype none
// This file is part of the ZXUNO Spectrum core.
// Creation date is 03:02:46 2017-02-13 by Miguel Angel Rodriguez Jodar
// (c)2014-2020 ZXUNO association.
// ZXUNO official repository: http://svn.zxuno.com/svn/zxuno
// Username: guest Password: zxuno
// Github repository for this core: https://github.com/mcleod-ideafix/zxuno_spectrum_core
//
// ZXUNO Spectrum core 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.
//
// ZXUNO Spectrum core 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 the ZXUNO Spectrum core. If not, see <https://www.gnu.org/licenses/>.
//
// Any distributed copy of this file must keep this notice intact.
module dma (
input wire clk,
input wire rst_n,
input wire [7:0] zxuno_addr,
input wire regaddr_changed,
input wire zxuno_regrd,
input wire zxuno_regwr,
input wire [7:0] din,
output reg [7:0] dout,
output reg oe,
//---- DMA bus -----
input wire m1_n,
output reg busrq_n,
input wire busak_n,
output reg [15:0] dma_a,
input wire [7:0] dma_din,
output reg [7:0] dma_dout,
output reg dma_mreq_n,
output reg dma_iorq_n,
output reg dma_rd_n,
output reg dma_wr_n
);
`include "config.vh"
localparam
NODMA = 3'd0,
DOBURST = 3'd1,
DOTIMED = 3'd2,
DOTIMED_2 = 3'd3,
DOTRANSFER = 3'd4,
TRANSFER_2 = 3'd5,
TRANSFER_3 = 3'd6;
reg [2:0] iocnt = 3'b000;
reg [1:0] mode = 2'b00; // 00: apagado, 01: burst sin reinicio, 10: timed, sin reinicio, 11: timed, con reinicio
reg [1:0] srcdst = 2'b00; // 00: memoria a memoria, 01: memoria a I/O, 10: I/O a memoria, 11: I/O a I/O
reg select_addr_to_reach = 1'b0; // 0: el bit 7 de DMASTAT obedece a la direccion fuente, 1: obedece a la direcci<63>n destino
reg [15:0] src = 16'h0000, dst = 16'h0000;
reg [15:0] srcidx = 16'h0000, dstidx = 16'h0000;
reg [15:0] preescaler = 16'h0000;
reg [15:0] cntpreescaler = 16'h0000;
reg [15:0] transferlength = 16'h0000;
reg [15:0] cnttransfers = 16'h0000;
reg [15:0] addrtoreach = 16'h0000;
reg [2:0] state = NODMA;
reg [2:0] returnstate = NODMA;
reg [7:0] data;
reg data_received = 1'b0;
reg addr_is_reached = 1'b0;
reg hilo = 1'b0; // flipflop para determinar qu<71> cacho de dato se va a dar en una lectura
reg read_in_progress = 1'b0;
initial busrq_n = 1'b1;
initial dma_mreq_n = 1'b1;
initial dma_iorq_n = 1'b1;
initial dma_rd_n = 1'b1;
initial dma_wr_n = 1'b1;
// CPU reads DMA registers
always @* begin
oe = 1'b0;
dout = 8'hFF;
if (zxuno_addr == DMACTRL && zxuno_regrd == 1'b1) begin
dout = {3'b000, select_addr_to_reach, srcdst, mode};
oe = 1'b1;
end
else if (zxuno_addr == DMASTAT && zxuno_regrd == 1'b1) begin
dout = {addr_is_reached,7'b0000000};
oe = 1'b1;
end
else if (zxuno_addr == DMASRC && zxuno_regrd == 1'b1) begin
dout = (hilo)? src[15:8]:src[7:0];
oe = 1'b1;
end
else if (zxuno_addr == DMADST && zxuno_regrd == 1'b1) begin
dout = (hilo)? dst[15:8]:dst[7:0];
oe = 1'b1;
end
else if (zxuno_addr == DMAPRE && zxuno_regrd == 1'b1) begin
dout = (hilo)? preescaler[15:8]:preescaler[7:0];
oe = 1'b1;
end
else if (zxuno_addr == DMALEN && zxuno_regrd == 1'b1) begin
dout = (hilo)? transferlength[15:8]:transferlength[7:0];
oe = 1'b1;
end
else if (zxuno_addr == DMAPROB && zxuno_regrd == 1'b1) begin
dout = (hilo)? addrtoreach[15:8]:addrtoreach[7:0];
oe = 1'b1;
end
end
// CPU writes DMA registers
always @(posedge clk) begin
iocnt <= iocnt + 3'd1; // free running 3-bit counter
if (rst_n == 1'b0) begin
data_received <= 1'b0;
mode <= 2'b00;
srcdst <= 2'b00;
select_addr_to_reach <= 1'b0;
preescaler <= 16'h0000;
transferlength <= 16'h0000;
addrtoreach <= 16'h0000;
src <= 16'h0000;
dst <= 16'h0000;
hilo <= 1'b0;
read_in_progress <= 1'b0;
end
else begin
if (regaddr_changed == 1'b1) begin
hilo <= 1'b0;
read_in_progress <= 1'b0;
end
else if (zxuno_regrd == 1'b1 && (zxuno_addr == DMASRC || zxuno_addr == DMADST || zxuno_addr == DMAPRE || zxuno_addr == DMALEN || zxuno_addr == DMAPROB || zxuno_addr == DMASTAT))
read_in_progress <= 1'b1;
else if (read_in_progress == 1'b1 && zxuno_regrd == 1'b0) begin
hilo <= ~hilo;
read_in_progress <= 1'b0;
if (zxuno_addr == DMASTAT)
addr_is_reached <= 1'b0; // resetear direccion alcanzada despu<70>s de haber sido leido
end
if (zxuno_addr == DMACTRL && zxuno_regwr == 1'b1)
{select_addr_to_reach,srcdst,mode} <= din[4:0];
else if (zxuno_regwr == 1'b1 && (zxuno_addr == DMASRC || zxuno_addr == DMADST || zxuno_addr == DMAPRE || zxuno_addr == DMALEN || zxuno_addr == DMAPROB)) begin
data_received <= 1'b1;
data <= din;
end
else if (data_received == 1'b1 && zxuno_regwr == 1'b0) begin // just after the I/O write operation has finished, 16-bit registers are updated
case (zxuno_addr)
DMASRC : src <= {data, src[15:8]};
DMADST : dst <= {data, dst[15:8]};
DMAPRE : preescaler <= {data, preescaler[15:8]};
DMALEN : transferlength <= {data, transferlength[15:8]};
DMAPROB: addrtoreach <= {data, addrtoreach[15:8]};
endcase
data_received <= 1'b0;
end
end
// DMA FSM
if (rst_n == 1'b0) begin
busrq_n <= 1'b1;
dma_mreq_n <= 1'b1;
dma_iorq_n <= 1'b1;
dma_rd_n <= 1'b1;
dma_wr_n <= 1'b1;
cntpreescaler <= 16'h0000;
cnttransfers <= 16'h0000;
state <= NODMA;
end
else begin
if (srcdst == 2'b00 || iocnt == 3'b000) begin
if (cntpreescaler == 16'h0000)
cntpreescaler <= preescaler;
else
cntpreescaler <= cntpreescaler + 16'hFFFF ; // -1
case (state)
NODMA:
begin
if (mode == 2'b01/* && m1_n == 1'b0*/) begin
state <= DOBURST;
srcidx <= src;
dstidx <= dst;
busrq_n <= 1'b0;
cnttransfers <= transferlength;
end
else if (mode == 2'b10 || mode == 2'b11) begin
state <= DOTIMED;
srcidx <= src;
dstidx <= dst;
cntpreescaler <= preescaler;
cnttransfers <= transferlength;
end
else
busrq_n <= 1'b1;
end
DOBURST:
begin
if (busak_n == 1'b0) begin
if (cnttransfers == 16'h0000) begin
state <= NODMA;
mode <= 2'b00; // clear transfer mode
busrq_n <= 1'b1;
end
else begin
state <= DOTRANSFER;
dma_a <= srcidx;
if (srcdst[1] == 1'b0)
dma_mreq_n <= 1'b0;
else
dma_iorq_n <= 1'b0;
dma_rd_n <= 1'b0;
returnstate <= DOBURST;
end
end
end
DOTIMED:
begin
if (mode == 2'b00) begin
state <= NODMA;
busrq_n <= 1'b1;
end
else if (cntpreescaler == 16'h0000) begin
busrq_n <= 1'b0;
state <= DOTIMED_2;
end
else
busrq_n <= 1'b1;
end
DOTIMED_2:
begin
if (busak_n == 1'b0) begin
if (cnttransfers != 16'h0000) begin
state <= DOTRANSFER;
dma_a <= srcidx;
if (srcdst[1] == 1'b0)
dma_mreq_n <= 1'b0;
else
dma_iorq_n <= 1'b0;
dma_rd_n <= 1'b0;
returnstate <= DOTIMED;
end
else if (mode == 2'b11) begin
cnttransfers <= transferlength; // reiniciar timed con reinicio
srcidx <= src;
dstidx <= dst;
end
else begin
mode <= 2'b00; // fin de timed sin reinicio
busrq_n <= 1'b1;
state <= NODMA;
end
end
end
//--- One transfer ---
DOTRANSFER:
begin
dma_dout <= dma_din;
dma_rd_n <= 1'b1;
dma_mreq_n <= 1'b1;
dma_iorq_n <= 1'b1;
dma_a <= dstidx;
state <= TRANSFER_2;
if ((select_addr_to_reach == 1'b0 && srcidx == addrtoreach) || (select_addr_to_reach == 1'b1 && dstidx == addrtoreach))
addr_is_reached <= 1'b1;
end
TRANSFER_2:
begin
if (srcdst[0] == 1'b0) begin
dma_mreq_n <= 1'b0;
end
else begin
dma_iorq_n <= 1'b0;
end
dma_wr_n <= 1'b0;
state <= TRANSFER_3;
end
TRANSFER_3:
begin
dma_mreq_n <= 1'b1;
dma_iorq_n <= 1'b1;
dma_wr_n <= 1'b1;
cnttransfers <= cnttransfers + 16'hFFFF; // -1
if (srcdst[1] == 1'b0)
srcidx <= srcidx + 16'd1;
if (srcdst[0] == 1'b0)
dstidx <= dstidx + 16'd1;
state <= returnstate;
end
default:
state <= NODMA;
endcase
end
end
end
endmodule