module dataController_top(
	// clocks:
	input clk,
	input cep,
	input cen,

	// system control:
	input _systemReset,

	// 68000 CPU control:
	output _cpuReset,
	output [2:0] _cpuIPL,

	// 68000 CPU memory interface:
	input [15:0] cpuDataIn,
	input [3:0] cpuAddrRegHi, // A12-A9
	input [2:0] cpuAddrRegMid, // A6-A4
	input [1:0] cpuAddrRegLo, // A2-A1
	input _cpuUDS,
	input _cpuLDS,	
	input _cpuRW,
	output reg [15:0] cpuDataOut,

	// peripherals:
	input selectSCSI,
	input selectSCC,
	input selectIWM,
	input selectVIA,

	// RAM/ROM:
	input cpuBusControl,	
	input [15:0] memoryDataIn,
	output [15:0] memoryDataOut,

	// keyboard:
	input [10:0] ps2_key,
	output       capslock,

	// mouse:
	input [24:0] ps2_mouse,

	// serial:
	input serialIn, 
	output serialOut,	

	// video:
	input _hblank,
	input _vblank,

	// audio
	output [10:0] audioOut,  // 8 bit audio + 3 bit volume
	output snd_alt,
	input loadSound,

	// misc
	output memoryOverlayOn,
	input [1:0] insertDisk,
	input [1:0] diskSides,
	output [1:0] diskEject,

	output [1:0] diskMotor,
	output [1:0] diskAct,

	output [21:0] dskReadAddrInt,
	input dskReadAckInt,
	output [21:0] dskReadAddrExt,
	input dskReadAckExt,

	// connections to io controller
   output [31:0] io_lba,
   output 	     io_rd,
   output 	     io_wr,
   input 	     io_ack,

	input   [8:0] sd_buff_addr,
	input   [7:0] sd_buff_dout,
	output  [7:0] sd_buff_din,
	input         sd_buff_wr
);

// add binary volume levels according to volume setting
assign audioOut = 
	(snd_vol[0] ? audio_latch    : 11'd0) +
	(snd_vol[1] ? audio_latch<<1 : 11'd0) +
	(snd_vol[2] ? audio_latch<<2 : 11'd0);

reg [10:0] audio_latch;
always @(posedge clk) begin
	reg loadSoundD;
	if(cen) loadSoundD <= loadSound;
	if(cep && loadSoundD) begin
		if(snd_ena) audio_latch <= 0;
		else  	 	audio_latch <= memoryDataIn[15:8];
	end
end

// CPU reset generation
// For initial CPU reset, RESET and HALT must be asserted for at least 100ms = 800,000 clocks of clk8
reg [19:0] resetDelay; // 20 bits = 1 million
wire isResetting = resetDelay != 0;

initial begin
	// force a reset when the FPGA configuration is completed
	resetDelay <= 20'hFFFFF;
end

always @(posedge clk or negedge _systemReset) begin
	if (_systemReset == 1'b0) begin
		resetDelay <= 20'hFFFFF;
	end
	else if(cep && isResetting) begin
		resetDelay <= resetDelay - 1'b1;
	end
end
assign _cpuReset = isResetting ? 1'b0 : 1'b1;

// interconnects
wire SEL;
wire _viaIrq, _sccIrq, sccWReq;
wire [15:0] viaDataOut;
wire [15:0] iwmDataOut;
wire [7:0] sccDataOut;
wire [7:0] scsiDataOut;
wire mouseX1, mouseX2, mouseY1, mouseY2, mouseButton;

// interrupt control
assign _cpuIPL = 
	!_viaIrq?3'b110:
	!_sccIrq?3'b101:
	3'b111;

// Serial port
assign serialOut = 0;

// CPU-side data output mux
always @(*) begin
	casex({selectIWM, selectVIA, selectSCC, selectSCSI})
		'b1xxx: cpuDataOut = iwmDataOut;
		'b01xx: cpuDataOut = viaDataOut;
		'b001x: cpuDataOut = { sccDataOut, 8'hEF };
		'b0001: cpuDataOut = { scsiDataOut, 8'hEF };
		'b0000: cpuDataOut = memoryDataIn;
	endcase
end

// Memory-side
assign memoryDataOut = cpuDataIn;

// SCSI
ncr5380 scsi
(
	.clk(clk),
	.ce(cep),

	.reset(!_cpuReset),
	.bus_cs(selectSCSI && cpuBusControl),
	.bus_we(!_cpuRW),
	.bus_rs(cpuAddrRegMid),
	.dack(cpuAddrRegHi[0]),   // A9
	.wdata(cpuDataIn[15:8]),
	.rdata(scsiDataOut),

	// connections to io controller
	.io_lba ( io_lba ),
	.io_rd ( io_rd ),
	.io_wr ( io_wr ),
	.io_ack ( io_ack ),

	.sd_buff_addr(sd_buff_addr),
	.sd_buff_dout(sd_buff_dout),
	.sd_buff_din(sd_buff_din),
	.sd_buff_wr(sd_buff_wr)
);


// VIA
wire [2:0] snd_vol;
wire snd_ena;

via v
(
	.clk(clk),
	.cep(cep),
	.cen(cen),

	._reset(_cpuReset),
	.selectVIA(selectVIA && cpuBusControl),
	._cpuRW(_cpuRW),
	._cpuUDS(_cpuUDS),	
	.dataIn(cpuDataIn),
	.cpuAddrRegHi(cpuAddrRegHi),
	._hblank(_hblank),
	._vblank(_vblank),
	.mouseY2(mouseY2),
	.mouseX2(mouseX2),
	.mouseButton(mouseButton),
	.sccWReq(sccWReq),
	._irq(_viaIrq),
	.dataOut(viaDataOut),
	.memoryOverlayOn(memoryOverlayOn),
	.SEL(SEL),

	.snd_vol(snd_vol),
	.snd_ena(snd_ena),
	.snd_alt(snd_alt),

	.kbd_in_data(kbd_in_data),
	.kbd_in_strobe(kbd_in_strobe),
	.kbd_out_data(kbd_out_data),
	.kbd_out_strobe(kbd_out_strobe)
);

// IWM
iwm i
(
	.clk(clk),
	.cep(cep),
	.cen(cen),

	._reset(_cpuReset),
	.selectIWM(selectIWM && cpuBusControl),
	._cpuRW(_cpuRW),
	._cpuLDS(_cpuLDS),
	.dataIn(cpuDataIn),
	.cpuAddrRegHi(cpuAddrRegHi),
	.SEL(SEL),
	.dataOut(iwmDataOut),
	.insertDisk(insertDisk),
	.diskSides(diskSides),
	.diskEject(diskEject),
	.diskMotor(diskMotor),
	.diskAct(diskAct),

	.dskReadAddrInt(dskReadAddrInt),
	.dskReadAckInt(dskReadAckInt),
	.dskReadAddrExt(dskReadAddrExt),
	.dskReadAckExt(dskReadAckExt),
	.dskReadData(memoryDataIn[7:0])
);

// SCC
scc s
(
	.clk(clk),
	.cep(cep),
	.cen(cen),

	.reset_hw(~_cpuReset),
	.cs(selectSCC && (_cpuLDS == 1'b0 || _cpuUDS == 1'b0) && cpuBusControl),
	.we(!_cpuRW),
	.rs(cpuAddrRegLo), 
	.wdata(cpuDataIn[15:8]),
	.rdata(sccDataOut),
	._irq(_sccIrq),
	.dcd_a(mouseX1),
	.dcd_b(mouseY1),
	.wreq(sccWReq)
);

// Mouse
ps2_mouse mouse
(
	.reset(~_cpuReset),

	.clk(clk),
	.ce(cep),

	.ps2_mouse(ps2_mouse),

	.x1(mouseX1),
	.y1(mouseY1),
	.x2(mouseX2),
	.y2(mouseY2),
	.button(mouseButton)
);

wire [7:0] kbd_in_data;
wire       kbd_in_strobe;
wire [7:0] kbd_out_data;
wire       kbd_out_strobe;

// Keyboard
ps2_kbd kbd
(
	.reset(~_cpuReset),

	.clk(clk),
	.ce(cep),

	.ps2_key(ps2_key),
	.capslock(capslock),

	.data_out(kbd_out_data),              // data from mac
	.strobe_out(kbd_out_strobe),
	.data_in(kbd_in_data),         // data to mac
	.strobe_in(kbd_in_strobe)
);

endmodule
