Files
TSConf_MiST/rtl/dram/arbiter.v
Eugene Lozovoy 66bfce85d5 add gs support
2024-09-14 16:41:57 +03:00

272 lines
9.9 KiB
Verilog

`include "tune.v"
// PentEvo project (c) NedoPC 2008-2011
//
// DRAM arbiter. Shares DRAM between CPU, video data fetcher and other devices
//
// Arbitration is made on full 8-cycle access blocks. Each cycle is defined by dram.v and consists of 4 fpga clocks.
// During each access block, there can be either no videodata access, 1 videodata access, 2, 4 or 8 accesses.
// All spare cycles can be used by CPU or other devices. If no device uses memory in the given cycle, refresh cycle is performed.
//
// In each access block, videodata accesses are spreaded all over the block so that CPU receives cycle
// as fast as possible, until there is absolute need to fetch remaining video data.
//
// Examples:
//
// | access block | 4 video accesses during block, no processor accesses. video accesses are done
// | vid | vid | vid | vid | ref | ref | ref | ref | as soon as possible, spare cycles are refresh ones
//
// | access block | 4 video accesses during block, processor requests access every other cycle
// | vid | prc | vid | prc | vid | prc | vid | prc |
//
// | access block | 4 video accesses, processor begins requesting cycles continously from second one
// | vid | prc | prc | prc | prc | vid | vid | vid | so it is given cycles while there is such possibility. after that processor
// can't access mem until the end of access block and stalls
//
// | access block | 8 video accesses, processor stalls, if it is requesting cycles
// | vid | vid | vid | vid | vid | vid | vid | vid |
//
// | access block | 2 video accesses, single processor request, other cycles are refresh ones
// | vid | vid | ref | ref | cpu | ref | ref | ref |
//
// | access block | 4 video accesses, single processor request, other cycles are refresh ones
// | vid | vid | cpu | vid | vid | ref | ref | ref |
//
// access block begins at any dram cycle, then blocks video_go back-to-back
//
// key signals are video_go and XXX_req, sampled at the end of each dram cycle. Must be set to the module at c3 clock cycle.
// CPU can have either normal or lower access priority to the DRAM.
// At the INT active (32 of 3.5MHz clocks) the priority is raised to normal, so that CPU won't miss its interrupt.
// This should be considered if dummy RAM access used for waiting for the end of DMA operation instead of status bit polling.
//
// DRAM access priority:
// Z80 normal Z80 low
// - VIDEO - VIDEO
// - CPU - TS
// - TM - TM
// - TS - DMA
// - DMA - CPU
module arbiter
(
input wire clk,
input wire c1,
input wire c2,
input wire c3,
input wire cyc,
// dram.v interface
output wire [22:0] dram_addr, // address for dram access
output wire dram_req, // dram request
output wire dram_rnw, // Read-NotWrite
output wire [ 1:0] dram_bsel, // byte select: bsel[1] for wrdata[15:8], bsel[0] for wrdata[7:0]
output wire [15:0] dram_wrdata, // data to be written
// video
input wire [20:0] video_addr, // during access block, only when video_strobe==1
input wire video_go, // start video access blocks
input wire [ 4:0] video_bw, // [4:3] - total cycles: 11 = 8 / 01 = 4 / 00 = 2
// [2:0] - need cycles
output wire video_pre_next, // (c1)
output wire video_next, // (c2) at this signal video_addr may be changed; it is one clock leading the video_strobe
output wire video_strobe, // (c3) one-cycle strobe meaning that video_data is available
output wire next_vid, // used for TM prefetch
// CPU
input wire [20:0] cpu_addr,
input wire [ 7:0] cpu_wrdata,
input wire cpu_req,
input wire cpu_rnw,
input wire cpu_wrbsel,
input wire cpu_csrom,
output reg cpu_next, // next cycle is allowed to be used by CPU
output reg cpu_strobe,
output reg cpu_latch,
output wire curr_cpu_o,
// DMA
input wire [20:0] dma_addr,
input wire [15:0] dma_wrdata,
input wire dma_req,
input wire dma_rnw,
output wire dma_next,
// TS
input wire [20:0] ts_addr,
input wire ts_req,
output wire ts_pre_next,
output wire ts_next,
// TM
input wire [20:0] tm_addr,
input wire tm_req,
output wire tm_next,
// ROM loader
input wire loader_clk,
input wire [15:0] loader_addr,
input wire [7:0] loader_data,
input wire loader_wr,
input wire loader_cs_rom_main,
input wire loader_cs_rom_gs
);
localparam CYCLES = 6;
localparam CYC_CPU = 6'b000001;
localparam CYC_VID = 6'b000010;
localparam CYC_TS = 6'b000100;
localparam CYC_TM = 6'b001000;
localparam CYC_DMA = 6'b010000;
localparam CYC_LOADER = 6'b100000;
localparam CYC_FREE = 6'b000000;
localparam CPU = 0;
localparam VIDEO = 1;
localparam TS = 2;
localparam TM = 3;
localparam DMA = 4;
localparam LOADER = 5;
reg [CYCLES-1:0] curr_cycle; // type of the cycle in progress
reg [CYCLES-1:0] next_cycle; // type of the next cycle
reg [2:0] blk_rem = 0; // remaining accesses in a block (7..0)
reg [2:0] vid_rem = 0; // remaining video accesses in block
reg stall = 0;
wire dev_over_cpu = 0; // can be used to rise devices priority over CPU
wire next_cpu = next_cycle[CPU];
assign next_vid = next_cycle[VIDEO];
wire next_ts = next_cycle[TS];
wire next_tm = next_cycle[TM];
wire next_dma = next_cycle[DMA];
wire next_loader = next_cycle[LOADER];
wire curr_cpu = curr_cycle[CPU];
wire curr_vid = curr_cycle[VIDEO];
wire curr_ts = curr_cycle[TS];
wire curr_tm = curr_cycle[TM];
wire curr_dma = curr_cycle[DMA];
wire curr_loader = curr_cycle[LOADER];
assign curr_cpu_o = curr_cpu;
// track blk_rem counter:
// how many cycles left to the end of block (7..0)
wire video_start = ~|blk_rem;
wire [2:0] blk_nrem = (video_start && video_go) ? {video_bw[4:3], 1'b1} : (video_start ? 3'd0 : (blk_rem - 3'd1));
wire bw_full = ~|{video_bw[4] & video_bw[2], video_bw[3] & video_bw[1], video_bw[0]}; // stall when 000/00/0
wire video_only = stall || (vid_rem == blk_rem);
wire video_idle = ~|vid_rem;
always @(posedge clk) if (c3)
begin
blk_rem <= blk_nrem;
if (video_start)
stall <= bw_full & video_go;
end
// track vid_rem counter
// how many video cycles left to the end of block (7..0)
wire [2:0] vidmax = {video_bw[2:0]}; // number of cycles for video access
wire [2:0] vid_nrem_next = video_idle ? 3'd0 : (vid_rem - 3'd1);
wire [2:0] vid_nrem_start = (cpu_req && !dev_over_cpu) ? vidmax : (vidmax - 3'd1);
wire [2:0] vid_nrem = (video_go && video_start) ? vid_nrem_start : (next_vid ? vid_nrem_next : vid_rem);
always @(posedge clk) if (c3)
vid_rem <= vid_nrem;
reg loader_wr0;
reg [7:0] loader_data0;
reg [1:0] loader_hiaddr;
always @(posedge loader_clk) begin
if (loader_wr && (loader_cs_rom_main || loader_cs_rom_gs)) begin
loader_wr0 <= 1'd1;
loader_data0 <= loader_data;
loader_hiaddr <= { loader_cs_rom_gs, loader_cs_rom_main };
end
else if (cyc) begin
loader_wr0 <= 1'd0;
end
end
// next cycle decision
wire [CYCLES-1:0] cyc_dev = tm_req ? CYC_TM : (ts_req ? CYC_TS : CYC_DMA);
wire dev_req = ts_req || tm_req || dma_req;
always @*
if (loader_wr0) begin
cpu_next = 1'b0;
next_cycle = CYC_LOADER;
end
else
if (video_start) // video burst start
if (video_go) // video active
begin
cpu_next = dev_over_cpu ? 1'b0 : !bw_full;
next_cycle = dev_over_cpu ? CYC_VID : (bw_full ? CYC_VID : (cpu_req ? CYC_CPU : CYC_VID));
end
else // video idle
begin
cpu_next = !dev_over_cpu;
next_cycle = dev_over_cpu ? cyc_dev : (cpu_req ? CYC_CPU : (dev_req ? cyc_dev : CYC_FREE));
end
else // video burst in progress
begin
cpu_next = dev_over_cpu ? 1'b0 : !video_only;
next_cycle = video_only ? CYC_VID : (dev_over_cpu ? cyc_dev : (cpu_req ? CYC_CPU : (!video_idle ? CYC_VID : (dev_req ? cyc_dev : CYC_FREE))));
end
always @(posedge clk) if (c3)
curr_cycle <= next_cycle;
// DRAM interface
assign dram_wrdata = curr_loader? {2{loader_data0}} : curr_dma ? dma_wrdata : {2{cpu_wrdata[7:0]}}; // write data has to be clocked at c0 in dram.v
assign dram_bsel[1:0] = next_loader? {loader_addr[0], ~loader_addr[0]} : next_dma ? 2'b11 : {cpu_wrbsel, ~cpu_wrbsel};
assign dram_req = |next_cycle;
assign dram_rnw = next_loader? 1'b0 : next_cpu ? cpu_rnw : (next_dma ? dma_rnw : 1'b1);
assign dram_addr = {23{next_loader}} & { loader_hiaddr, 6'b000000, loader_addr[15:1] }
| {23{next_cpu}} & { 1'b0, cpu_csrom, {6{~cpu_csrom}} & cpu_addr[20:15], cpu_addr[14:0] }
| {23{next_vid}} & { 2'b0, video_addr }
| {23{next_ts}} & { 2'b0, ts_addr }
| {23{next_tm}} & { 2'b0, tm_addr }
| {23{next_dma}} & { 2'b0, dma_addr };
reg cpu_rnw_r;
always @(posedge clk) if (c3)
cpu_rnw_r <= cpu_rnw;
// read strobes generation for video and cpu
always @(posedge clk)
if (c1)
begin
cpu_strobe <= curr_cpu && cpu_rnw_r;
cpu_latch <= curr_cpu && cpu_rnw_r;
end
else if (c2)
cpu_strobe <= 1'b0;
else if (c3)
cpu_latch <= 1'b0;
assign video_pre_next = curr_vid & c1;
assign video_next = curr_vid & c2;
assign video_strobe = curr_vid && c3;
assign ts_pre_next = curr_ts & c1;
assign ts_next = curr_ts & c2;
assign tm_next = curr_tm & c2;
assign dma_next = curr_dma & c2;
endmodule