module abcbus (
	       input		 rst_n,
	       input		 sys_clk,
	       input		 sdram_clk, // Assumed to be a multiple of sys_clk
	       input		 stb_1mhz, // 1-2 MHz sys_clk strobe

	       // CPU interface
	       input		 abc_valid, // Control/status registers
	       input		 map_valid, // Memory map
	       input [31:0]	 cpu_addr,
	       input [31:0]	 cpu_wdata,
	       input [3:0]	 cpu_wstrb,
	       output reg [31:0] cpu_rdata, // For the ABC-bus control
	       output [31:0]	 cpu_rdata_map, // For the map RAM
	       output reg	 irq,

	       // ABC bus
	       input		 abc_clk,
	       input [15:0]	 abc_a,
	       inout [7:0]	 abc_d,
	       output reg	 abc_d_oe,
	       input		 abc_rst_n,
	       input		 abc_cs_n,
	       input [4:0]	 abc_out_n,
	       input [1:0]	 abc_inp_n,
	       input		 abc_xmemfl_n,
	       input		 abc_xmemw800_n, // Memory write strobe (ABC800)
	       input		 abc_xmemw80_n, // Memory write strobe (ABC80)
	       input		 abc_xinpstb_n, // I/O read strobe (ABC800)
	       input		 abc_xoutpstb_n, // I/O write strobe (ABC80)
	       // The following are inverted versus the bus IF
	       // the corresponding MOSFETs are installed
	       output		 abc_rdy_x, // RDY = WAIT#
	       output		 abc_resin_x, // System reset request
	       output		 abc_int80_x, // System INT request (ABC80)
	       output		 abc_int800_x, // System INT request (ABC800)
	       output		 abc_nmi_x, // System NMI request (ABC800)
	       output		 abc_xm_x, // System memory override (ABC800)
	       // Host/device control
	       output		 abc_host, // 1 = host, 0 = device
	       output reg	 abc_a_oe,
	       // Bus isolation
	       output		 abc_d_ce_n,

	       // ABC-bus extension header
	       // (Note: cannot use an array here because HC and HH are
	       // input only.)
	       inout		 exth_ha,
	       inout		 exth_hb,
	       input		 exth_hc,
	       inout		 exth_hd,
	       inout		 exth_he,
	       inout		 exth_hf,
	       inout		 exth_hg,
	       input		 exth_hh,

	       // SDRAM interface
	       output [24:0]	 sdram_addr,
	       input [7:0]	 sdram_rd,
	       output reg	 sdram_valid,
	       input		 sdram_ready,
	       output [7:0]	 sdram_wd,
	       output reg	 sdram_wstrb
	       );

   // Set if MOSFETs Q1-Q6 are installed rather than the corresponding
   // resistors. BOTH CANNOT BE INSTALLED AT THE SAME TIME.
   parameter [6:1] mosfet_installed = 6'b111_111;
   parameter [0:0] exth_reversed = 1'b0;

   // Synchronizer for ABC-bus input signals; also changes
   // the sense to positive logic where applicable
   wire	       abc_clk_s;
   wire [15:0] abc_a_s;
   wire [7:0]  abc_di;
   wire        abc_rst_s;
   wire        abc_cs_s;
   wire [4:0]  abc_out_s;
   wire [1:0]  abc_inp_s;
   wire        abc_xmemfl_s;
   wire        abc_xmemw800_s;
   wire        abc_xmemw80_s;
   wire        abc_xinpstb_s;
   wire        abc_xoutpstb_s;

   synchronizer #( .width(39) ) abc_synchro
     (
      .rst_n ( rst_n ),
      .clk ( sys_clk ),
      .d ( { abc_clk, abc_a, abc_d, ~abc_rst_n, ~abc_cs_n,
	     ~abc_out_n, ~abc_inp_n, ~abc_xmemfl_n, ~abc_xmemw800_n,
	     ~abc_xmemw80_n, ~abc_xinpstb_n, ~abc_xoutpstb_n } ),
      .q ( { abc_clk_s, abc_a_s, abc_di, abc_rst_s, abc_cs_s,
	     abc_out_s, abc_inp_s, abc_xmemfl_s, abc_xmemw800_s,
	     abc_xmemw80_s, abc_xinpstb_s, abc_xoutpstb_s } )
      );

   assign abc_host = 1'b0;	// Only device mode supported
   assign abc_d_ce_n = 1'b0;	// Do not isolate busses
   assign abc_a_oe   = 1'b0;	// Only device mode, again...

   reg	     abc_clk_active;

   // On ABC800, only one of XINPSTB# or XOUTPSTB# will be active;
   // on ABC80 they will either be 00 or ZZ; in the latter case pulled
   // low by external resistors.
   wire      abc80  = abc_xinpstb_s & abc_xoutpstb_s;
   wire      abc800 = ~abc80;

   // Memory and I/O read/write strobes for ABC-bus
   reg	     abc_xmemrd;
   reg       abc_xmemwr;
   reg [1:0] abc_inp;
   reg [4:0] abc_out;
   reg	     abc_rst;
   reg	     abc_cs;
   reg [3:1] abc_stb;		// Delayed strobes

   always @(posedge sdram_clk)
     begin
	abc_xmemrd <= abc_clk_active & abc_xmemfl_s;
	abc_xmemwr <= abc_clk_active &
	(abc800 ? abc_xmemw800_s : abc_xmemw80_s);
	abc_inp <= abc_inp_s & {2{abc_clk_active}};
	abc_out <= abc_out_s & {5{abc_clk_active}};
	abc_rst <= abc_rst_s & abc_clk_active;
	abc_cs  <= abc_cs_s  & abc_clk_active;
	abc_stb <= { abc_stb, |{abc_inp, abc_out, abc_rst,
				abc_cs, abc_xmemrd, abc_xmemwr} };
     end

   reg  [7:0] abc_do;
   assign abc_d    = abc_d_oe ? abc_do : 8'hzz;

   reg  [8:0] ioselx;
   wire       iosel_en = ioselx[8];
   wire [5:0] iosel = ioselx[5:0];

   // ABC-bus I/O select
   always @(negedge rst_n or posedge sdram_clk)
     if (~rst_n)
       ioselx <= 9'b0;
     else if (abc_rst)
       ioselx <= 9'b0;
     else if (abc_cs)
       ioselx <= { 1'b1, abc_di };

   // Open drain signals with optional MOSFETs
   reg	      abc_wait  = 1'b1;	// Power up asserted; see below
   reg	      abc_int   = 1'b0;
   reg	      abc_nmi   = 1'b0;
   reg	      abc_resin = 1'b0;
   reg	      abc_xm    = 1'b0;

   function reg opt_mosfet(input signal, input mosfet);
      if (mosfet)
	opt_mosfet = signal;
      else
	opt_mosfet = signal ? 1'b0 : 1'bz;
   endfunction // opt_mosfet

   assign abc_int80_x  = opt_mosfet(abc_int & abc80, mosfet_installed[1]);
   assign abc_rdy_x    = opt_mosfet(abc_wait, mosfet_installed[2]);
   assign abc_nmi_x    = opt_mosfet(abc_nmi, mosfet_installed[3]);
   assign abc_resin_x  = opt_mosfet(abc_resin, mosfet_installed[4]);
   assign abc_int800_x = opt_mosfet(abc_int & abc800, mosfet_installed[5]);
   assign abc_xm_x     = opt_mosfet(abc_xm, mosfet_installed[6]);

   // Detect ABC-bus clock: need a minimum frequency of 84/64 MHz
   // to be considered live.
   reg [2:0] abc_clk_ctr;
   reg [1:0] abc_clk_q;

   always @(negedge rst_n or posedge sys_clk)
     if (~rst_n)
       begin
	  abc_clk_q      <= 2'b0;
	  abc_clk_ctr    <= 3'b0;
	  abc_clk_active <= 1'b0;
       end
     else
       begin
	  abc_clk_q <= { abc_clk_q[0], abc_clk_s };
	  case ( { abc_clk_q == 2'b10, stb_1mhz } )
	    5'b10: begin
	       if (abc_clk_ctr == 3'b111)
		 abc_clk_active <= 1'b1;
	       else
		 abc_clk_ctr <= abc_clk_ctr + 1'b1;
	    end
	    5'b01: begin
	       if (abc_clk_ctr == 3'b000)
		 abc_clk_active <= 1'b0;
	       else
		 abc_clk_ctr <= abc_clk_ctr - 1'b1;
	    end
	    default: begin
	       // nothing
	    end
	  endcase // case ( {(abc_clk_q == 2'10), sys_clk_stb[6]} )
       end // else: !if(~rst_n)

   // ABC-bus extension header (exth_c and exth_h are input only)
   // The naming of pins is kind of nonsensical:
   //
   //       +3V3 -  1  2 - +3V3
   //         HA -  3  4 - HE
   //         HB -  5  6 - HG
   //         HC -  7  8 - HH
   //         HD -  9 10 - HF
   //        GND - 11 12 - GND
   //
   // This layout allows the header to be connected on either side
   // of the board. This logic assigns the following names to the pins;
   // if the ext_reversed is set to 1 then the left and right sides
   // are flipped.
   //
   //      +3V3  -  1  2 - +3V3
   //    exth[0] -  3  4 - exth[1]
   //    exth[2] -  5  6 - exth[3]
   //    exth[6] -  7  8 - exth[7]
   //    exth[4] -  9 10 - exth[5]
   //        GND - 11 12 - GND

   wire [7:0] exth_d;	// Input data
   wire [5:0] exth_q;	// Output data
   wire [5:0] exth_oe;	// Output enable

   assign exth_d[0]    = exth_reversed ? exth_he : exth_ha;
   assign exth_d[1]    = exth_reversed ? exth_ha : exth_he;
   assign exth_d[2]    = exth_reversed ? exth_hg : exth_hb;
   assign exth_d[3]    = exth_reversed ? exth_hb : exth_hg;
   assign exth_d[4]    = exth_reversed ? exth_hf : exth_hd;
   assign exth_d[5]    = exth_reversed ? exth_hd : exth_hf;
   assign exth_d[6]    = exth_reversed ? exth_hh : exth_hc;
   assign exth_d[7]    = exth_reversed ? exth_hc : exth_hh;

   wire [2:0] erx      = { 2'b00, exth_reversed };
   assign exth_ha      = exth_oe[3'd0 ^ erx] ? exth_q[3'd0 ^ erx] : 1'bz;
   assign exth_he      = exth_oe[3'd1 ^ erx] ? exth_q[3'd1 ^ erx] : 1'bz;
   assign exth_hb      = exth_oe[3'd2 ^ erx] ? exth_q[3'd2 ^ erx] : 1'bz;
   assign exth_hg      = exth_oe[3'd3 ^ erx] ? exth_q[3'd3 ^ erx] : 1'bz;
   assign exth_hd      = exth_oe[3'd4 ^ erx] ? exth_q[3'd4 ^ erx] : 1'bz;
   assign exth_hf      = exth_oe[3'd5 ^ erx] ? exth_q[3'd5 ^ erx] : 1'bz;

   assign exth_q  = 6'b0;
   assign exth_oe = 6'b0;

   // ABC SDRAM interface
   //
   // Memory map for ABC-bus memory references.
   // 512 byte granularity for memory (registers 0-127),
   // one input and one output queue per select code for I/O (128-255).
   //
   // bit [15:0]  = SDRAM address [24:9] ( bits 24:9 from CPU )
   // bit [16]    = write enable ( bit 30 from CPU )
   // bit [17]    = read enable  ( bit 31 from CPU )
   //
   // Accesses from the internal CPU supports 32-bit accesses only!
   //
   wire [17:0] rdata_abcmemmap;
   wire [17:0] abc_memmap_rd;

   abcmapram abcmapram (
			.aclr      ( ~rst_n ),

			.clock     ( sdram_clk ),

			.address_a ( abc_a_s[15:9] ),
			.data_a    ( 18'bx ),
			.wren_a    ( 1'b0 ),
			.q_a       ( abc_memmap_rd ),

			.address_b ( cpu_addr[8:2] ),
			.data_b    ( { cpu_wdata[31:30], cpu_wdata[24:9] } ),
			.wren_b    ( map_valid & cpu_wstrb[0] ),

			.q_b       ( rdata_abcmemmap )
			);

   assign cpu_rdata_map =  { rdata_abcmemmap[17:16], 5'b0,
			     rdata_abcmemmap[15:0], 9'b0 };

   wire        abc_rden    = abc_memmap_rd[17];
   wire        abc_wren    = abc_memmap_rd[16];
   wire [24:0] abc_memaddr = { abc_memmap_rd[15:0], abc_a_s[8:0] };

   reg	      abc_memrd_en;
   reg	      abc_memwr_en;
   reg	      abc_do_memrd;
   reg	      abc_do_memwr;

   always @(posedge sdram_clk or negedge rst_n)
     if (~rst_n)
       begin
	  abc_memrd_en   <= 1'b0;
	  abc_memwr_en   <= 1'b0;
	  abc_do_memrd   <= 1'b0;
	  abc_do_memwr   <= 1'b0;
	  sdram_valid    <= 1'b0;
	  sdram_wstrb    <= 1'b0;
       end
     else
       begin
	  // Careful with the registering here: need to make sure
	  // abcmapram is caught up for I/O; for memory the address
	  // will have been stable for some time
	  abc_memwr_en  <= abc_xmemwr;
	  abc_memrd_en  <= abc_xmemrd;

	  abc_do_memrd  <= abc_rden & abc_memrd_en;
	  abc_do_memwr  <= abc_wren & abc_memwr_en;

	  sdram_valid   <= abc_do_memrd | abc_do_memwr;
	  sdram_wstrb   <= abc_do_memwr;
       end // else: !if(~rst_n)

   assign sdram_addr = abc_memaddr;
   assign sdram_wd   = abc_di;

   // I/O data registers; RST# is considered OUT 7 even through
   // it is an IN from the ABC point of view.
   //
   // OUT register, written from ABC: <addr 2:0>   <data 7:0>
   // IN register,  written from CPU: <enable 1:0> <status 7:0> <inp 7:0>
   // Busy register:
   //
   //   [7:0] - busy OUT status (write-1-clear)
   //   [9:8] - busy IN status  (write-1-clear)
   // [15:12] - bus status change (write-1-clear)
   //           same bit positions as the bus status register
   //
   // [23:16] - busy OUT mask
   // [25:24] - busy IN mask
   // [31:28] - bus status change IRQ enable
   //
   // Assert WAIT# (deassert RDY) if the masked busy status is nonzero
   // and an busy-unmasked I/O comes in.
   //
   // An IRQ is generated if the masked busy status is nonzero.
   //
   reg [9:0] busy_status;
   reg [9:0] busy_mask;
   reg [9:0] busy_io_q;
   reg [1:0] inp_en;
   reg [3:0] bus_change_status;
   reg [3:0] bus_change_mask;

   wire [9:0] is_io = { abc_inp[1:0], abc_rst, 1'b0,
			abc_out[4:1], abc_cs, abc_out[0] };

   wire [9:0] busy_io = is_io & busy_mask;
   wire       is_busy = |(busy_status & busy_mask);

   wire [9:0] busy_valid = 10'b11_1011_1111;

   wire [9:0] set_busy = busy_io_q & ~busy_io;

   always @(posedge sys_clk or negedge rst_n)
     if (~rst_n)
       busy_io_q <= 10'b0;
     else
       busy_io_q <= busy_io;

   // WAIT# logic
   reg	      abc_wait_force = 1'b1; // Power up asserted; ignores rst_n

   always @(posedge sys_clk)
     abc_wait <= abc_wait_force | (rst_n & |set_busy & is_busy);

   //
   // I/O data registers
   //
   reg [2:0]  reg_out_addr;
   reg [7:0]  reg_out_data;
   reg [7:0]  reg_inp_data[0:1];

   // OUT logic
   always @(posedge sdram_clk)
     begin
	if (|busy_io[7:0])
	  begin
	     reg_out_data <= abc_di;
	     case (busy_io[7:0])
	       8'b0000_0001: reg_out_addr <= 3'd0;
	       8'b0000_0010: reg_out_addr <= 3'd1;
	       8'b0000_0100: reg_out_addr <= 3'd2;
	       8'b0000_1000: reg_out_addr <= 3'd3;
	       8'b0001_0000: reg_out_addr <= 3'd4;
	       8'b0010_0000: reg_out_addr <= 3'd5;
	       8'b0100_0000: reg_out_addr <= 3'd6;
	       8'b1000_0000: reg_out_addr <= 3'd7;
	       default:      reg_out_addr <= 3'dx;
	     endcase // case (busy_io)
	  end // if (|busy_io[7:0])
     end // always @ (posedge sdram_clk)

   //
   // ABC data out (= ABC host read) logic
   //
   always @(negedge rst_n or posedge sdram_clk)
     if (~rst_n)
       begin
	  abc_d_oe <= 1'b0;
	  abc_do   <= 8'bx;
       end
     else
       begin
	  abc_d_oe <= 1'b0;
	  abc_do   <= 8'bx;

	  if (abc_do_memrd & sdram_ready)
	    begin
	       abc_d_oe <= 1'b1;
	       abc_do   <= sdram_rd;
	    end
	  else if (abc_inp[0] & inp_en[0])
	    begin
	       abc_d_oe <= 1'b1;
	       abc_do   <= reg_inp_data[0];
	    end
	  else if (abc_inp[1] & inp_en[1])
	    begin
	       abc_d_oe <= 1'b1;
	       abc_do   <= reg_inp_data[1];
	    end
       end // else: !if(~rst_n)

   // Bus status
   reg  [3:0] abc_status[0:1];

   always @(posedge sys_clk)
     begin
	abc_status[0] <= { 1'b0, abc800, abc_rst_s, abc_clk_active };
	abc_status[1] <= abc_status[0];
     end

   wire [3:0] bus_change = (abc_status[0] ^ abc_status[1]) & bus_change_mask;
   wire [3:0] bus_change_valid = 4'b0111;


   //
   // Busy/IRQ status and CPU register writes
   //
   always @(posedge sys_clk or negedge rst_n)
     if (~rst_n)
       begin
	  busy_status       <= 10'b0;
	  busy_mask         <= 10'h082; // Enable hold on RST# and CS#
	  inp_en            <= 2'b00;
	  bus_change_status <= 4'b0;
	  bus_change_mask   <= 4'b0;

	  // abc_resin, nmi, int and force_wait are deliberately not affected
	  // by an internal CPU reset. They are, however, inherently asserted
	  // when the FPGA is configured, and initialized to fixed values
	  // at configuration time (RESIN# asserted, the others deasserted.)
       end
     else
       begin
	  busy_status <= busy_status | set_busy;
	  bus_change_status <= bus_change_status | bus_change;

	  if (abc_valid)
	    begin
	       casez (cpu_addr[5:2] )
		 5'b??010: begin
		   if (cpu_wstrb[0])
		     busy_status[7:0] <= set_busy[7:0] | (busy_status[7:0] & ~cpu_wdata[7:0]);
		    if (cpu_wstrb[1])
		      begin
			 busy_status[9:8] <= set_busy[9:8] | (busy_status[9:8] & ~cpu_wdata[9:8]);
			 bus_change_status <= bus_change | (bus_change_status & ~cpu_wdata[15:12]);
		      end
		    if (cpu_wstrb[2])
		      busy_mask[7:0] <= cpu_wdata[23:16] & busy_valid[7:0];
		    if (cpu_wstrb[3])
		      begin
			 busy_mask[9:8]  <= cpu_wdata[25:24] & busy_valid[9:8];
			 bus_change_mask <= cpu_wdata[31:28] & bus_change_valid;
		      end
		 end
		 5'b??011: begin
		    if (cpu_wstrb[0])
		      begin
			 abc_resin      <= cpu_wdata[3];
			 abc_nmi        <= cpu_wdata[2];
			 abc_int        <= cpu_wdata[1];
			 abc_wait_force <= cpu_wdata[0];
		      end
		 end
		 5'b??101: begin
		    if (cpu_wstrb[0])
		      reg_inp_data[0] <= cpu_wdata[7:0];
		    if (cpu_wstrb[1])
		      reg_inp_data[1] <= cpu_wdata[15:8];
		    if (cpu_wstrb[2])
		      inp_en <= cpu_wdata[17:16];
		 end
		 default:
		   /* do nothing */ ;
	       endcase // casez (cpu_addr[5:2])
	    end // if (abc_valid & cpu_wstrb[0])
       end

   // Level triggered IRQ
   always @(posedge sys_clk)
     irq <= is_busy | (bus_change_status & bus_change_mask);

   // Read MUX
   always_comb
     casez (cpu_addr[5:2])
       5'b00000: cpu_rdata = { 28'b0, abc_status[0] };
       5'b00001: cpu_rdata = { 23'b0, ~iosel_en, ioselx[7:0] };
       5'b00010: cpu_rdata = { bus_change_mask, 2'b0, busy_mask,
			       bus_change_status, 2'b0, busy_status };
       5'b00011: cpu_rdata = { 28'b0, abc_resin, abc_nmi, abc_int, abc_wait };
       5'b00100: cpu_rdata = { 21'b0, reg_out_addr, reg_out_data };
       5'b00101: cpu_rdata = { 14'b0, inp_en, reg_inp_data[1], reg_inp_data[0] };
       default:  cpu_rdata = 32'bx;
     endcase // casez (cpu_addr[5:2])

endmodule // abcbus
