From 5adbe31b0356474fb1330bf34243593177ed69ca Mon Sep 17 00:00:00 2001 From: Greg Taylor Date: Fri, 12 Apr 2024 21:36:12 -0400 Subject: [PATCH] Use async fifo for host_if, add trick_sw_detection logic (#31) * fix timers, add detect opl3 testbench * comments * convert host_if to fifo design * added trick_sw_detection logic * added some color to comments --- fpga/Makefile | 4 +- fpga/modules/clks/src/reset_sync.sv | 2 +- fpga/modules/host_if/src/host_if.sv | 119 +-- .../modules/host_if/src/trick_sw_detection.sv | 145 ++++ fpga/modules/misc/src/afifo.v | 814 ++++++++++++++++++ .../opl3_fpga_2_0/src/opl3_fpga_v2_0.sv | 1 - .../opl3_fpga_2_0/src/opl3_fpga_v2_0_S_AXI.v | 7 +- fpga/modules/timers/src/timer.sv | 25 +- fpga/modules/timers/src/timers.sv | 7 +- fpga/modules/top_level/pkg/opl3_pkg.sv | 2 +- fpga/modules/top_level/sim/opl3_tb.sv | 85 +- fpga/modules/top_level/src/opl3.sv | 8 +- 12 files changed, 1087 insertions(+), 132 deletions(-) create mode 100755 fpga/modules/host_if/src/trick_sw_detection.sv create mode 100644 fpga/modules/misc/src/afifo.v diff --git a/fpga/Makefile b/fpga/Makefile index 5c8e4d9..704555f 100755 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -69,13 +69,15 @@ RTL_SRC = \ modules/operator/src/opl3_exp_lut.sv \ modules/timers/src/timers.sv \ modules/timers/src/timer.sv \ + modules/misc/src/afifo.v \ modules/misc/src/edge_detector.sv \ modules/misc/src/mem_simple_dual_port.sv \ modules/misc/src/mem_multi_bank.sv \ modules/misc/src/pipeline_sr.sv \ modules/misc/src/synchronizer.sv \ modules/misc/src/leds.sv \ - modules/host_if/src/host_if.sv + modules/host_if/src/host_if.sv \ + modules/host_if/src/trick_sw_detection.sv PKG_SRC = \ modules/top_level/pkg/opl3_pkg.sv diff --git a/fpga/modules/clks/src/reset_sync.sv b/fpga/modules/clks/src/reset_sync.sv index 07e2816..b24cefc 100755 --- a/fpga/modules/clks/src/reset_sync.sv +++ b/fpga/modules/clks/src/reset_sync.sv @@ -53,7 +53,7 @@ module reset_sync ( output logic reset // synchronous active-high local reset ); (* ASYNC_REG = "true" *) - logic r0 = 0, r1 = 0, r2 = 0; + logic r0 = 1, r1 = 1, r2 = 1; always_ff @(posedge clk or negedge arst_n) if (!arst_n) begin diff --git a/fpga/modules/host_if/src/host_if.sv b/fpga/modules/host_if/src/host_if.sv index 43e5372..26e84d1 100755 --- a/fpga/modules/host_if/src/host_if.sv +++ b/fpga/modules/host_if/src/host_if.sv @@ -54,98 +54,49 @@ module host_if input wire [1:0] address, input wire [REG_FILE_DATA_WIDTH-1:0] din, output logic [REG_FILE_DATA_WIDTH-1:0] dout, - output logic ack_host_wr, output opl3_reg_wr_t opl3_reg_wr = 0, - input wire [REG_FILE_DATA_WIDTH-1:0] status + input wire [REG_FILE_DATA_WIDTH-1:0] status, + output logic force_timer_overflow ); - logic cs_p1 = 0; - logic cs_p2 = 0; - logic rd_p1 = 0; - logic wr_p1 = 0; - logic [1:0] address_p1 = 0; - logic [REG_FILE_DATA_WIDTH-1:0] din_p1; - logic cs_latched = 0; - logic rd_latched = 0; - logic wr_latched = 0; - logic [1:0] address_latched = 0; - logic [REG_FILE_DATA_WIDTH-1:0] din_latched = 0; - logic cs_opl3; - logic cs_opl3_p1 = 0; - logic rd_opl3 = 0; - logic wr_opl3 = 0; - logic [1:0] address_opl3 = 0; - logic [REG_FILE_DATA_WIDTH-1:0] din_opl3 = 0; - logic ack_transaction; - - /* - * Handshake signals across clock domains using cs signal - */ - always_ff @(posedge clk_host) begin - cs_p1 <= !cs_n; - cs_p2 <= cs_p1; - rd_p1 <= !rd_n; - wr_p1 <= !wr_n; - address_p1 <= address; - din_p1 <= din; + logic opl3_fifo_empty; + logic [1:0] opl3_address; + logic [REG_FILE_DATA_WIDTH-1:0] opl3_data; - if (cs_p1 && !cs_p2 && !cs_latched) begin - cs_latched <= 1; - rd_latched <= rd_p1; - wr_latched <= wr_p1; - address_latched <= address_p1; - din_latched <= din_p1; - end + logic wr; + logic wr_p1 = 0; - if (ack_transaction) begin - cs_latched <= 0; - rd_latched <= 0; - wr_latched <= 0; - end + always_comb wr = !cs_n && !wr_n; - if (!ic_n) begin - cs_latched <= 0; - rd_latched <= 0; - wr_latched <= 0; - address_latched <= 0; - din_latched <= 0; - end - end + always_ff @(posedge clk_host) + wr_p1 <= wr; - synchronizer cs_sync ( - .clk, - .in(cs_latched), - .out(cs_opl3) - ); - - synchronizer ack_sync ( - .clk(clk_host), - .in(cs_opl3), - .out(ack_transaction) - ); - - always_comb ack_host_wr = ack_transaction && wr_latched; + afifo #( + .WIDTH(2 + REG_FILE_DATA_WIDTH) // address + data + ) afifo ( + .i_wclk(clk_host), + .i_wr_reset_n(ic_n), + .i_wr(wr && !wr_p1), // edge detect if write is held for more than 1 cycle + .i_wr_data({address, din}), + .o_wr_full(), + .i_rclk(clk), + .i_rd_reset_n(!reset), + .i_rd(!opl3_fifo_empty), + .o_rd_data({opl3_address, opl3_data}), + .o_rd_empty(opl3_fifo_empty) + ); always_ff @(posedge clk) begin - cs_opl3_p1 <= cs_opl3; - rd_opl3 <= rd_latched; - wr_opl3 <= wr_latched; - address_opl3 <= address_latched; - din_opl3 <= din_latched; opl3_reg_wr.valid <= 0; - if (cs_opl3 && !cs_opl3_p1) - unique case ({rd_opl3, wr_opl3, address_opl3[0]}) - 'b010: begin // address write mode - opl3_reg_wr.bank_num <= address_opl3[1]; - opl3_reg_wr.address <= din_opl3; + if (!opl3_fifo_empty) + if (!opl3_address[0]) begin // address write mode + opl3_reg_wr.bank_num <= opl3_address[1]; + opl3_reg_wr.address <= opl3_data; end - 'b011: begin // data write mode - opl3_reg_wr.data <= din_opl3; + else begin // data write mode + opl3_reg_wr.data <= opl3_data; opl3_reg_wr.valid <= 1; end - 'b100:; // status read mode - default:; - endcase if (reset) opl3_reg_wr <= 0; @@ -159,5 +110,15 @@ module host_if .in(status), .out(dout) ); + + generate + if (INSTANTIATE_TIMERS) + trick_sw_detection trick_sw_detection ( + .* + ); + else + always_comb force_timer_overflow = 0; + endgenerate + endmodule `default_nettype wire \ No newline at end of file diff --git a/fpga/modules/host_if/src/trick_sw_detection.sv b/fpga/modules/host_if/src/trick_sw_detection.sv new file mode 100755 index 0000000..6eb323b --- /dev/null +++ b/fpga/modules/host_if/src/trick_sw_detection.sv @@ -0,0 +1,145 @@ +/******************************************************************************* +# +html+
+#
+#   FILENAME: trick_sw_detection.sv
+#   AUTHOR: Greg Taylor     CREATION DATE: 12 April 2024
+#
+#   DESCRIPTION:
+#   OPL detection logic trick. Some games will simply use reads to introduce a 80us delay. The problem is
+#   these reads may finish before 80us in a fast system, and the timer overflow would not have occured yet,
+#   thus failing the detection. Count the reads after a write to set the st1 register and trigger an overflow
+#   if a certain number have occured. Counter in clk_host domain, signal to timer is synchronized to OPL3 clk.
+#
+#   The detection method I've seen is something like this:
+#
+#        opl3_write('h04, 'h60, 'b0); // mask timers 1 and 2
+#        opl3_write('h04, 'h80, 'b0); // rst_irq, unmask timers
+#        opl3_read(stat1);
+#        opl3_write('h02, 'hff, 'b0); // set timer 1 to max value
+#        opl3_write('h04, 'h21, 'b0); // mask timer 2, start timer 1
+#        for (int i = 0; i < 200; ++i) // this is has to wait at least 80us, but reads may finish sooner
+#            opl3_read(dummy);
+#        opl3_read(stat2);
+#        opl3_write('h04, 'h60, 'b0);
+#        opl3_write('h04, 'h80, 'b0);
+#        if ((stat1 & 'he0) == 0 && (stat2 & 'he0) == 'hc0)
+#            $display("OPL3 detected!");
+#        else
+#            $error("OPL3 not detected...");
+#
+#   Think Volkswagon cheating on the diesel emmisions test by detecting they're under test, except this is to
+#   accomplish good. This works for Doom OPL detection under ao486MiSTer.
+#
+#   CHANGE HISTORY:
+#   12 April 2024    Greg Taylor
+#       Initial version
+#
+#   Copyright (C) 2014 Greg Taylor 
+#
+#   This file is part of OPL3 FPGA.
+#
+#   OPL3 FPGA is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU Lesser General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   OPL3 FPGA 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 Lesser General Public License for more details.
+#
+#   You should have received a copy of the GNU Lesser General Public License
+#   along with OPL3 FPGA.  If not, see .
+#
+#   Original Java Code:
+#   Copyright (C) 2008 Robson Cozendey 
+#
+#   Original C++ Code:
+#   Copyright (C) 2012  Steffen Ohrendorf 
+#
+#   Some code based on forum posts in:
+#   http://forums.submarine.org.uk/phpBB/viewforum.php?f=9,
+#   Copyright (C) 2010-2013 by carbon14 and opl3
+#
+#******************************************************************************/
+`timescale 1ns / 1ps
+`default_nettype none
+
+module trick_sw_detection
+    import opl3_pkg::*;
+(
+    input wire clk,
+    input wire clk_host,
+    input wire ic_n,
+    input wire cs_n,
+    input wire rd_n,
+    input wire wr_n,
+    input wire [1:0] address,
+    input wire [REG_FILE_DATA_WIDTH-1:0] din,
+    output logic force_timer_overflow
+);
+    localparam NUM_READS_TO_TRIGGER_OVERFLOW = 20;
+
+    logic wr;
+    logic wr_p1 = 0;
+    logic [$clog2(NUM_READS_TO_TRIGGER_OVERFLOW)-1:0] host_rd_counter = 0;
+    opl3_reg_wr_t host_reg_wr;
+    logic rd;
+    logic rd_p1 = 0;
+    logic host_force_timer_overflow = 0;
+    logic start_counter = 0;
+
+    always_comb wr = !cs_n && !wr_n;
+
+    always_ff @(posedge clk_host)
+        wr_p1 <= wr;
+
+    always_ff @(posedge clk_host) begin
+        host_reg_wr.valid <= 0;
+
+        if (wr && !wr_p1)
+            if (!address[0]) begin // address write mode
+                host_reg_wr.bank_num <= address[1];
+                host_reg_wr.address <= din;
+            end
+            else begin             // data write mode
+                host_reg_wr.valid <= 1;
+                host_reg_wr.data <= din;
+            end
+
+        if (!ic_n)
+            host_reg_wr <= 0;
+    end
+
+    always_comb rd = !cs_n && !rd_n;
+
+    always_ff @(posedge clk_host) begin
+        rd_p1 <= rd;
+
+        if (host_reg_wr.valid && host_reg_wr.bank_num == 0 && host_reg_wr.address == 'h4 && host_reg_wr.data[0]) begin
+            start_counter <= 1;
+            host_rd_counter <= 0;
+            host_force_timer_overflow <= 0;
+        end
+
+        if (start_counter && rd && !rd_p1)
+            host_rd_counter <= host_rd_counter + 1;
+
+        if (host_rd_counter == NUM_READS_TO_TRIGGER_OVERFLOW)
+            host_force_timer_overflow <= 1; // signal is held for synchronizer
+
+        if (!ic_n || (host_reg_wr.valid && !(host_reg_wr.bank_num == 0 && host_reg_wr.address == 'h4 && host_reg_wr.data[0]))) begin
+            start_counter <= 0;
+            host_rd_counter <= 0;
+            host_force_timer_overflow <= 0;
+        end
+    end
+
+    synchronizer force_timer_overflow_sync (
+        .clk,
+        .in(host_force_timer_overflow),
+        .out(force_timer_overflow)
+    );
+
+endmodule
+`default_nettype wire
\ No newline at end of file
diff --git a/fpga/modules/misc/src/afifo.v b/fpga/modules/misc/src/afifo.v
new file mode 100644
index 0000000..811a40e
--- /dev/null
+++ b/fpga/modules/misc/src/afifo.v
@@ -0,0 +1,814 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// Filename:	rtl/afifo.v
+// {{{
+// Project:	10Gb Ethernet switch
+//
+// Purpose:	A basic asynchronous FIFO.
+//
+// Creator:	Dan Gisselquist, Ph.D.
+//		Gisselquist Technology, LLC
+//
+////////////////////////////////////////////////////////////////////////////////
+// }}}
+// Copyright (C) 2023-2024, Gisselquist Technology, LLC
+// {{{
+// This file is part of the ETH10G project.
+//
+// The ETH10G project contains free software and gateware, licensed under the
+// terms of the 3rd version of the GNU General Public License as published by
+// the Free Software Foundation.
+//
+// This project is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY 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.  (It's in the $(ROOT)/doc directory.  Run make with no
+// target there if the PDF file isn't present.)  If not, see
+//  for a copy.
+// }}}
+// License:	GPL, v3, as defined and found on www.gnu.org,
+// {{{
+//		http://www.gnu.org/licenses/gpl.html
+//
+////////////////////////////////////////////////////////////////////////////////
+//
+`timescale		1ns/1ps
+`default_nettype	none
+// }}}
+module afifo #(
+		// {{{
+		// LGFIFO is the log based-two of the number of entries
+		//	in the FIFO, log_2(fifo size)
+		parameter	LGFIFO = 3,
+		//
+		// WIDTH is the number of data bits in each entry
+		parameter	WIDTH  = 16,
+		//
+		// NFF is the number of flip flops used to cross clock domains.
+		// 2 is a minimum.  Some applications appreciate the better
+		parameter	NFF    = 2,
+		//
+		// This core can either write on the positive edge of the clock
+		// or the negative edge.  Set WRITE_ON_POSEDGE (the default)
+		// to write on the positive edge of the clock.
+		parameter [0:0]	WRITE_ON_POSEDGE = 1'b1,
+		//
+		// Many  logic elements can read from memory asynchronously.
+		// This burdens any following logic.  By setting
+		// OPT_REGISTER_READS, we force all reads to be synchronous and
+		// not burdened by any logic.  You can spare a clock of latency
+		// by clearing this register.
+		parameter [0:0]	OPT_REGISTER_READS = 1'b1
+`ifdef	FORMAL
+		// F_OPT_DATA_STB
+		// {{{
+		// In the formal proof, F_OPT_DATA_STB includes a series of
+		// assumptions associated with a data strobe I/O pin--things
+		// like a discontinuous clock--just to make sure the core still
+		// works in those circumstances
+		, parameter [0:0]	F_OPT_DATA_STB = 1'b1
+		// }}}
+`endif
+		// }}}
+	) (
+		// {{{
+		//
+		// The (incoming) write data interface
+		input	wire			i_wclk,
+		// Verilator lint_off SYNCASYNCNET
+		input	wire			i_wr_reset_n,
+		// Verilator lint_on  SYNCASYNCNET
+		input	wire			i_wr,
+		input	wire	[WIDTH-1:0]	i_wr_data,
+		output	reg			o_wr_full,
+		//
+		// The (incoming) write data interface
+		input	wire			i_rclk,
+		// Verilator lint_off SYNCASYNCNET
+		input	wire			i_rd_reset_n,
+		// Verilator lint_on  SYNCASYNCNET
+		input	wire			i_rd,
+		output	reg	[WIDTH-1:0]	o_rd_data,
+		output	reg			o_rd_empty
+`ifdef	FORMAL
+		, output reg	[LGFIFO:0]	f_fill
+`endif
+		// }}}
+	);
+
+	// Register/net declarations
+	// {{{
+	// MSB = most significant bit of the FIFO address vector.  It's
+	// just short-hand for LGFIFO, and won't work any other way.
+	localparam	MSB = LGFIFO;
+	//
+	reg	[WIDTH-1:0]		mem	[(1<> 1);
+	end
+	// }}}
+
+	// Write to memory
+	// {{{
+	always @(posedge wclk)
+	if (i_wr && !o_wr_full)
+		mem[wr_addr[LGFIFO-1:0]] <= i_wr_data;
+	// }}}
+
+	// rd_addr, rgray
+	// {{{
+	assign	next_rd_addr = rd_addr + 1;
+	always @(posedge i_rclk or negedge i_rd_reset_n)
+	if (!i_rd_reset_n)
+	begin
+		rd_addr <= 0;
+		rgray   <= 0;
+	end else if (lcl_read && !lcl_rd_empty)
+	begin
+		rd_addr <= next_rd_addr;
+		rgray   <= next_rd_addr ^ (next_rd_addr >> 1);
+	end
+	// }}}
+
+	// Read from memory
+	// {{{
+	always @(*)
+		lcl_rd_data = mem[rd_addr[LGFIFO-1:0]];
+	// }}}
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Cross clock domains
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+
+	// read pointer -> wr_rgray
+	// {{{
+	always @(posedge wclk or negedge i_wr_reset_n)
+	if (!i_wr_reset_n)
+		{ wr_rgray, rgray_cross } <= 0;
+	else
+		{ wr_rgray, rgray_cross } <= { rgray_cross, rgray };
+	// }}}
+
+	// write pointer -> rd_wgray
+	// {{{
+	always @(posedge i_rclk or negedge i_rd_reset_n)
+	if (!i_rd_reset_n)
+		{ rd_wgray, wgray_cross } <= 0;
+	else
+		{ rd_wgray, wgray_cross } <= { wgray_cross, wgray };
+	// }}}
+
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Flag generation
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+
+	always @(*)
+		o_wr_full = (wr_rgray == { ~wgray[MSB:MSB-1], wgray[MSB-2:0] });
+
+	always @(*)
+		lcl_rd_empty = (rd_wgray == rgray);
+
+	// o_rd_empty, o_rd_data
+	// {{{
+	generate if (OPT_REGISTER_READS)
+	begin : GEN_REGISTERED_READ
+		// {{{
+		always @(*)
+			lcl_read = (o_rd_empty || i_rd);
+
+		always @(posedge i_rclk or negedge i_rd_reset_n)
+		if (!i_rd_reset_n)
+			o_rd_empty <= 1'b1;
+		else if (lcl_read)
+			o_rd_empty <= lcl_rd_empty;
+
+		always @(posedge i_rclk)
+		if (lcl_read)
+			o_rd_data <= lcl_rd_data;
+		// }}}
+	end else begin : GEN_COMBINATORIAL_FLAGS
+		// {{{
+		always @(*)
+			lcl_read = i_rd;
+
+		always @(*)
+			o_rd_empty = lcl_rd_empty;
+
+		always @(*)
+			o_rd_data = lcl_rd_data;
+		// }}}
+	end endgenerate
+	// }}}
+	// }}}
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+//
+// Formal properties
+// {{{
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
+`ifdef	FORMAL
+	// Start out with some register/net/macro declarations, f_past_valid,etc
+	// {{{
+`ifdef	AFIFO
+`define	ASSERT	assert
+`define	ASSUME	assume
+`else
+`define	ASSERT	assert
+`define	ASSUME	assert
+`endif
+
+	(* gclk *)	reg	gbl_clk;
+	reg			f_past_valid_gbl, f_past_valid_rd,
+				f_rd_in_reset, f_wr_in_reset;
+	reg	[WIDTH-1:0]	past_rd_data, past_wr_data;
+	reg			past_wr_reset_n, past_rd_reset_n,
+				past_rd_empty, past_wclk, past_rclk, past_rd;
+	reg	[(LGFIFO+1)*(NFF-1)-1:0]	f_wcross, f_rcross;
+	reg	[LGFIFO:0]	f_rd_waddr, f_wr_raddr;
+	reg	[LGFIFO:0]	f_rdcross_fill	[NFF-1:0];
+	reg	[LGFIFO:0]	f_wrcross_fill	[NFF-1:0];
+
+
+	initial	f_past_valid_gbl = 1'b0;
+	always @(posedge gbl_clk)
+		f_past_valid_gbl <= 1'b1;
+
+	initial	f_past_valid_rd = 1'b0;
+	always @(posedge i_rclk)
+		f_past_valid_rd <= 1'b1;
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Reset checks
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+	initial	f_wr_in_reset = 1'b1;
+	always @(posedge wclk or negedge i_wr_reset_n)
+	if (!i_wr_reset_n)
+		f_wr_in_reset <= 1'b1;
+	else
+		f_wr_in_reset <= 1'b0;
+
+	initial	f_rd_in_reset = 1'b1;
+	always @(posedge i_rclk or negedge i_rd_reset_n)
+	if (!i_rd_reset_n)
+		f_rd_in_reset <= 1'b1;
+	else
+		f_rd_in_reset <= 1'b0;
+
+	//
+	// Resets are ...
+	//	1. Asserted always initially, and ...
+	always @(*)
+	if (!f_past_valid_gbl)
+	begin
+		`ASSUME(!i_wr_reset_n);
+		`ASSUME(!i_rd_reset_n);
+	end
+
+	//	2. They only ever become active together
+	always @(*)
+	if (past_wr_reset_n && !i_wr_reset_n)
+		`ASSUME(!i_rd_reset_n);
+
+	always @(*)
+	if (past_rd_reset_n && !i_rd_reset_n)
+		`ASSUME(!i_wr_reset_n);
+
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Synchronous signal assumptions
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+
+	always @(posedge gbl_clk)
+	begin
+		past_wr_reset_n <= i_wr_reset_n;
+		past_rd_reset_n <= i_rd_reset_n;
+
+		past_wclk  <= wclk;
+		past_rclk  <= i_rclk;
+
+		past_rd      <= i_rd;
+		past_rd_data <= lcl_rd_data;
+		past_wr_data <= i_wr_data;
+
+		past_rd_empty<= lcl_rd_empty;
+	end
+
+	//
+	// Read side may be assumed to be synchronous
+	always @(*)
+	if (f_past_valid_gbl && i_rd_reset_n && (past_rclk || !i_rclk))
+		// i.e. if (!$rose(i_rclk))
+		`ASSUME(i_rd == past_rd);
+
+	always @(*)
+	if (f_past_valid_rd && !f_rd_in_reset && !lcl_rd_empty
+		&&(past_rclk || !i_rclk))
+	begin
+		`ASSERT(lcl_rd_data == past_rd_data);
+		`ASSERT(lcl_rd_empty == past_rd_empty);
+	end
+
+
+	generate if (F_OPT_DATA_STB)
+	begin
+
+		always @(posedge gbl_clk)
+			`ASSUME(!o_wr_full);
+
+		always @(posedge gbl_clk)
+		if (!i_wr_reset_n)
+			`ASSUME(!i_wclk);
+
+		always @(posedge gbl_clk)
+			`ASSUME(i_wr == i_wr_reset_n);
+
+		always @(posedge gbl_clk)
+		if ($changed(i_wr_reset_n))
+			`ASSUME($stable(wclk));
+
+	end endgenerate
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Fill checks
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+	always @(*)
+		f_fill = wr_addr - rd_addr;
+
+	always @(*)
+	if (!f_wr_in_reset)
+		`ASSERT(f_fill <= { 1'b1, {(MSB){1'b0}} });
+
+	always @(*)
+	if (wr_addr == rd_addr)
+		`ASSERT(lcl_rd_empty);
+
+	always @(*)
+	if ((!f_wr_in_reset && !f_rd_in_reset)
+			&& wr_addr == { ~rd_addr[MSB], rd_addr[MSB-1:0] })
+		`ASSERT(o_wr_full);
+
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Induction checks
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+
+	// f_wr_in_reset -- write logic is in its reset state
+	// {{{
+	always @(*)
+	if (f_wr_in_reset)
+	begin
+		`ASSERT(wr_addr == 0);
+		`ASSERT(wgray_cross == 0);
+
+		`ASSERT(rd_addr == 0);
+		`ASSERT(rgray_cross == 0);
+		`ASSERT(rd_wgray == 0);
+
+		`ASSERT(lcl_rd_empty);
+		`ASSERT(!o_wr_full);
+	end
+	// }}}
+
+	// f_rd_in_reset -- read logic is in its reset state
+	// {{{
+	always @(*)
+	if (f_rd_in_reset)
+	begin
+		`ASSERT(rd_addr == 0);
+		`ASSERT(rgray_cross == 0);
+		`ASSERT(rd_wgray == 0);
+
+		`ASSERT(lcl_rd_empty);
+	end
+	// }}}
+
+	// f_wr_raddr -- a read address to match the gray values
+	// {{{
+	always @(posedge wclk or negedge i_wr_reset_n)
+	if (!i_wr_reset_n)
+		{ f_wr_raddr, f_rcross } <= 0;
+	else
+		{ f_wr_raddr, f_rcross } <= { f_rcross, rd_addr };
+	// }}}
+
+	// f_rd_waddr -- a write address to match the gray values
+	// {{{
+	always @(posedge i_rclk or negedge i_rd_reset_n)
+	if (!i_rd_reset_n)
+		{ f_rd_waddr, f_wcross } <= 0;
+	else
+		{ f_rd_waddr, f_wcross } <= { f_wcross, wr_addr };
+	// }}}
+
+	integer	k;
+
+	// wgray check
+	// {{{
+	always @(*)
+		`ASSERT((wr_addr ^ (wr_addr >> 1)) == wgray);
+	// }}}
+
+	// wgray_cross check
+	// {{{
+	always @(*)
+	for(k=0; k>1))
+			== wgray_cross[k*(LGFIFO+1) +: LGFIFO+1]);
+	// }}}
+
+	// rgray check
+	// {{{
+	always @(*)
+		`ASSERT((rd_addr ^ (rd_addr >> 1)) == rgray);
+	// }}}
+
+	// rgray_cross check
+	// {{{
+	always @(*)
+	for(k=0; k>1))
+			== rgray_cross[k*(LGFIFO+1) +: LGFIFO+1]);
+	// }}}
+
+	// wr_rgray
+	// {{{
+	always @(*)
+		`ASSERT((f_wr_raddr ^ (f_wr_raddr >> 1)) == wr_rgray);
+	// }}}
+
+	// rd_wgray
+	// {{{
+	always @(*)
+		`ASSERT((f_rd_waddr ^ (f_rd_waddr >> 1)) == rd_wgray);
+	// }}}
+
+	// f_rdcross_fill
+	// {{{
+	always @(*)
+	for(k=0; k= f_wrcross_fill[k-1]);
+	always @(*)
+		`ASSERT(f_wrcross_fill[0] >= f_fill);
+	// }}}
+
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Clock generation
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+
+	// Here's the challenge: if we use $past with any of our clocks, such
+	// as to determine stability or any such, the proof takes forever, and
+	// we need to guarantee a minimum number of transitions within the
+	// depth of the proof.  If, on the other hand, we build our own $past
+	// primitives--we can then finish much faster and be successful on
+	// any depth of proof.
+
+	// pre_xclk is what the clock will become on the next global clock edge.
+	// By using it here, we can check things @(*) instead of
+	// @(posedge gbl_clk).  Further, we can check $rose(pre_xclk) (or $fell)
+	// and essentially check things @(*) but while using @(global_clk).
+	// In other words, we can transition on @(posedge gbl_clk), but stay
+	// in sync with the data--rather than being behind by a clock.
+	// now_xclk is what the clock is currently.
+	//
+	(* anyseq *)	reg	pre_wclk, pre_rclk;
+			reg	now_wclk, now_rclk;
+	always @(posedge gbl_clk)
+	begin
+		now_wclk <= pre_wclk;
+		now_rclk <= pre_rclk;
+	end
+
+	always @(*)
+	begin
+		assume(i_wclk == now_wclk);
+		assume(i_rclk == now_rclk);
+	end
+
+	always @(posedge gbl_clk)
+		assume(i_rclk == $past(pre_rclk));
+
+	// Assume both clocks start idle
+	// {{{
+	always @(*)
+	if (!f_past_valid_gbl)
+	begin
+		assume(!pre_wclk && !wclk);
+		assume(!pre_rclk && !i_rclk);
+	end
+	// }}}
+
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Formal contract check --- the twin write test
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+
+	// Tracking register declarations
+	// {{{
+			reg	[WIDTH-1:0]	f_first, f_next;
+	(* anyconst *)	reg	[LGFIFO:0]	f_addr;
+			reg	[LGFIFO:0]	f_next_addr;
+	reg			f_first_in_fifo, f_next_in_fifo;
+	reg	[LGFIFO:0]	f_to_first, f_to_next;
+	reg	[1:0]	f_state;
+
+	always @(*)
+		f_next_addr = f_addr + 1;
+	// }}}
+
+	// distance_to*, *_in_fifo
+	// {{{
+	always @(*)
+	begin
+		f_to_first = f_addr - rd_addr;
+
+		f_first_in_fifo = 1'b1;
+		if ((f_to_first >= f_fill)||(f_fill == 0))
+			f_first_in_fifo = 1'b0;
+
+		if (mem[f_addr] != f_first)
+			f_first_in_fifo = 1'b0;
+
+		//
+		// Check the second item
+		//
+
+		f_to_next  = f_next_addr - rd_addr;
+		f_next_in_fifo = 1'b1;
+		if ((f_to_next >= f_fill)||(f_fill == 0))
+			f_next_in_fifo = 1'b0;
+
+		if (mem[f_next_addr] != f_next)
+			f_next_in_fifo = 1'b0;
+	end
+	// }}}
+
+	// f_state -- generate our state variable
+	// {{{
+	initial	f_state = 0;
+	always @(posedge gbl_clk)
+	if (!i_wr_reset_n)
+		f_state <= 0;
+	else case(f_state)
+	2'b00: if (($rose(pre_wclk))&& i_wr && !o_wr_full &&(wr_addr == f_addr))
+		begin
+			f_state <= 2'b01;
+			f_first <= i_wr_data;
+		end
+	2'b01: if ($rose(pre_rclk)&& lcl_read && rd_addr == f_addr)
+			f_state <= 2'b00;
+		else if ($rose(pre_wclk) && i_wr && !o_wr_full )
+		begin
+			f_state <= 2'b10;
+			f_next  <= i_wr_data;
+		end
+	2'b10: if ($rose(pre_rclk) && lcl_read && !lcl_rd_empty && rd_addr == f_addr)
+		f_state <= 2'b11;
+	2'b11: if ($rose(pre_rclk) && lcl_read && !lcl_rd_empty && rd_addr == f_next_addr)
+		f_state <= 2'b00;
+	endcase
+	// }}}
+
+	// f_state invariants
+	// {{{
+	always @(*)
+	if (i_wr_reset_n) case(f_state)
+	2'b00: begin end
+	2'b01: begin
+		`ASSERT(f_first_in_fifo);
+		`ASSERT(wr_addr == f_next_addr);
+		`ASSERT(f_fill >= 1);
+		end
+	2'b10: begin
+		`ASSERT(f_first_in_fifo);
+		`ASSERT(f_next_in_fifo);
+		if (!lcl_rd_empty && (rd_addr == f_addr))
+			`ASSERT(lcl_rd_data == f_first);
+		`ASSERT(f_fill >= 2);
+		end
+	2'b11: begin
+		`ASSERT(rd_addr == f_next_addr);
+		`ASSERT(f_next_in_fifo);
+		`ASSERT(f_fill >= 1);
+		if (!lcl_rd_empty)
+			`ASSERT(lcl_rd_data == f_next);
+		end
+	endcase
+	// }}}
+
+	generate if (OPT_REGISTER_READS)
+	begin
+		reg	past_o_rd_empty;
+
+		always @(posedge gbl_clk)
+			past_o_rd_empty <= o_rd_empty;
+
+		always @(posedge gbl_clk)
+		if (f_past_valid_gbl && i_rd_reset_n)
+		begin
+			if ($past(!o_rd_empty && !i_rd && i_rd_reset_n))
+				`ASSERT($stable(o_rd_data));
+		end
+
+		always @(posedge gbl_clk)
+		if (!f_rd_in_reset && i_rd_reset_n && i_rclk && !past_rclk)
+		begin
+			if (past_o_rd_empty)
+				`ASSERT(o_rd_data == past_rd_data);
+			if (past_rd)
+				`ASSERT(o_rd_data == past_rd_data);
+		end
+
+	end endgenerate
+	// }}}
+	////////////////////////////////////////////////////////////////////////
+	//
+	// Cover checks
+	// {{{
+	////////////////////////////////////////////////////////////////////////
+	//
+	//
+
+	// Prove that we can read and write the FIFO
+	// {{{
+	always @(*)
+	if (i_wr_reset_n && i_rd_reset_n)
+	begin
+		cover(o_rd_empty);
+		cover(!o_rd_empty);
+		cover(f_state == 2'b01);
+		cover(f_state == 2'b10);
+		cover(f_state == 2'b11);
+		cover(&f_fill[MSB-1:0]);
+
+		cover(i_rd);
+		cover(i_rd && !o_rd_empty);
+	end
+	// }}}
+
+`ifdef	AFIFO
+	generate if (!F_OPT_DATA_STB)
+	begin : COVER_FULL
+		// {{{
+		reg	cvr_full;
+
+		initial	cvr_full = 1'b0;
+		always @(posedge gbl_clk)
+		if (!i_wr_reset_n)
+			cvr_full <= 1'b0;
+		else if (o_wr_full)
+			cvr_full <= 1'b1;
+
+
+		always @(*)
+		if (f_past_valid_gbl && i_wr_reset_n)
+		begin
+			cover(o_wr_full);
+			cover(o_rd_empty && cvr_full);
+			cover(o_rd_empty && f_fill == 0 && cvr_full);
+		end
+		// }}}
+	end else begin : COVER_NEARLY_FULL
+		// {{{
+		reg	cvr_nearly_full;
+
+		initial	cvr_nearly_full = 1'b0;
+		always @(posedge gbl_clk)
+		if (!i_wr_reset_n)
+			cvr_nearly_full <= 1'b0;
+		else if (f_fill == { 1'b0, {(LGFIFO){1'b1} }})
+			cvr_nearly_full <= 1'b1;
+
+
+		always @(*)
+		if (f_past_valid_gbl && i_wr_reset_n)
+		begin
+			cover(f_fill == { 1'b0, {(LGFIFO){1'b1} }});
+			cover(cvr_nearly_full && i_wr_reset_n);
+			cover(o_rd_empty && cvr_nearly_full);
+			cover(o_rd_empty && f_fill == 0 && cvr_nearly_full);
+		end
+		// }}}
+	end endgenerate
+`endif
+	// }}}
+`endif
+// }}}
+endmodule
diff --git a/fpga/modules/opl3_fpga_2_0/src/opl3_fpga_v2_0.sv b/fpga/modules/opl3_fpga_2_0/src/opl3_fpga_v2_0.sv
index 5412927..d9bf1d8 100755
--- a/fpga/modules/opl3_fpga_2_0/src/opl3_fpga_v2_0.sv
+++ b/fpga/modules/opl3_fpga_2_0/src/opl3_fpga_v2_0.sv
@@ -41,7 +41,6 @@ module opl3_fpga_v2_0 #(
     logic [1:0] address;
     logic [7:0] din;
     logic [7:0] dout;
-    logic ack_host_wr;
     logic sample_valid;
     logic signed [23:0] sample_l;
     logic signed [23:0] sample_r;
diff --git a/fpga/modules/opl3_fpga_2_0/src/opl3_fpga_v2_0_S_AXI.v b/fpga/modules/opl3_fpga_2_0/src/opl3_fpga_v2_0_S_AXI.v
index 7b01331..78538ae 100644
--- a/fpga/modules/opl3_fpga_2_0/src/opl3_fpga_v2_0_S_AXI.v
+++ b/fpga/modules/opl3_fpga_2_0/src/opl3_fpga_v2_0_S_AXI.v
@@ -85,8 +85,7 @@
 		output reg wr_n,
 		output reg [1:0] address,
 		output reg [7:0] din,
-		input wire [7:0] dout,
-		input wire ack_host_wr
+		input wire [7:0] dout
 	);
 
 	// AXI4LITE signals
@@ -136,7 +135,7 @@
 		axi_awready <= 0;
 
 		if (slv_reg_wren)
-			axi_awready <= ack_host_wr;
+			axi_awready <= 1;
 		else if (slv_reg_rden)
 			axi_awready <= 1;
 	end
@@ -147,7 +146,7 @@
 	// de-asserted when reset is low.
 
 	always @(posedge S_AXI_ACLK)
-		axi_wready = ack_host_wr;
+		axi_wready <= slv_reg_wren;
 
 	// Implement memory mapped register select and write logic generation
 	// The write data is accepted and written to memory mapped registers when
diff --git a/fpga/modules/timers/src/timer.sv b/fpga/modules/timers/src/timer.sv
index b39db64..c24e209 100755
--- a/fpga/modules/timers/src/timer.sv
+++ b/fpga/modules/timers/src/timer.sv
@@ -49,7 +49,7 @@ module timer
     input wire clk,
     input wire [REG_TIMER_WIDTH-1:0] timer_reg,
     input wire start_timer,
-    output logic timer_overflow_pulse
+    output logic timer_overflow_pulse = 0
 );
     localparam int TICK_TIMER_COUNT_VALUE = CLK_FREQ*TIMER_TICK_INTERVAL;
 
@@ -57,6 +57,7 @@ module timer
     logic tick_pulse = 0;
     logic [REG_TIMER_WIDTH-1:0] timer = 0;
     logic start_timer_set_pulse;
+    logic [REG_TIMER_WIDTH-1:0] timer_p1 = 0;
 
     /*
      * Detect when start_timer is initially set, use it to reset the timer value
@@ -64,7 +65,7 @@ module timer
      */
     edge_detector #(
         .EDGE_LEVEL(1),
-        .CLK_DLY(1)
+        .CLK_DLY(0)
     ) start_timer_edge_detect (
         .clk(clk),
         .clk_en(1'b1),
@@ -75,11 +76,12 @@ module timer
     always_comb tick_pulse = tick_counter == TICK_TIMER_COUNT_VALUE - 1;
 
     always_ff @(posedge clk)
-        if (start_timer)
-            if (tick_pulse)
+        if (start_timer) begin
+            if (tick_pulse || start_timer_set_pulse)
                 tick_counter <= 0;
             else
                 tick_counter <= tick_counter + 1;
+        end
 
     /*
      * Timer gets set to timer_reg upon overflow
@@ -87,20 +89,15 @@ module timer
     always_ff @(posedge clk)
         if (start_timer_set_pulse)
             timer <= timer_reg;
-        else if (tick_pulse)
+        else if (tick_pulse) begin
             if (timer == 2**REG_TIMER_WIDTH - 1)
                 timer <= timer_reg;
             else
                 timer <= timer + 1;
+        end
+
+    always_ff @(posedge clk)
+        timer_overflow_pulse <= (timer == 2**REG_TIMER_WIDTH - 1) && tick_pulse;
 
-    edge_detector #(
-        .EDGE_LEVEL(1),
-        .CLK_DLY(1)
-    ) time_overflow_edge_detect (
-        .clk(clk),
-        .clk_en(1'b1),
-        .in(timer == 2**REG_TIMER_WIDTH - 1),
-        .edge_detected(timer_overflow_pulse)
-    );
 endmodule
 `default_nettype wire
\ No newline at end of file
diff --git a/fpga/modules/timers/src/timers.sv b/fpga/modules/timers/src/timers.sv
index 1ba8128..d6d2ec1 100755
--- a/fpga/modules/timers/src/timers.sv
+++ b/fpga/modules/timers/src/timers.sv
@@ -48,7 +48,8 @@ module timers
     input wire reset,
     input var opl3_reg_wr_t opl3_reg_wr,
     output logic irq_n = 0,
-    output logic [REG_FILE_DATA_WIDTH-1:0] status
+    output logic [REG_FILE_DATA_WIDTH-1:0] status,
+    input wire force_timer_overflow // comes from trick_sw_detection logic
 );
     logic [REG_TIMER_WIDTH-1:0] timer1 = 0;
     logic [REG_TIMER_WIDTH-1:0] timer2 = 0;
@@ -110,10 +111,10 @@ module timers
     );
 
     always_ff @(posedge clk) begin
-        if (timer1_overflow_pulse && mt1)
+        if ((timer1_overflow_pulse || force_timer_overflow) && !mt1)
             ft1 <= 1;
 
-        if (timer2_overflow_pulse && mt2)
+        if (timer2_overflow_pulse && !mt2)
             ft2 <= 1;
 
         if (reset || irq_rst) begin
diff --git a/fpga/modules/top_level/pkg/opl3_pkg.sv b/fpga/modules/top_level/pkg/opl3_pkg.sv
index d16051e..908bba9 100755
--- a/fpga/modules/top_level/pkg/opl3_pkg.sv
+++ b/fpga/modules/top_level/pkg/opl3_pkg.sv
@@ -51,7 +51,7 @@ package opl3_pkg;
      */
     localparam CLK_FREQ = 12.727e6;
     localparam DAC_OUTPUT_WIDTH = 24;
-    localparam INSTANTIATE_TIMERS = 0; // set to 1 to use timers, 0 to save area
+    localparam INSTANTIATE_TIMERS = 1; // set to 1 to use timers, 0 to save area
     localparam NUM_LEDS = 4; // connected to kon bank 0 starting at 0
     localparam INSTANTIATE_SAMPLE_SYNC_TO_CPU_CLK = 0;
 
diff --git a/fpga/modules/top_level/sim/opl3_tb.sv b/fpga/modules/top_level/sim/opl3_tb.sv
index 0648297..6f5a66d 100755
--- a/fpga/modules/top_level/sim/opl3_tb.sv
+++ b/fpga/modules/top_level/sim/opl3_tb.sv
@@ -46,7 +46,7 @@ module opl3_tb
 ();
     localparam HOST_CLK_FREQ = 100e6;
     localparam HOST_CLK_HALF_PERIOD = 1/real'(HOST_CLK_FREQ)*1000e6/2;
-    localparam OPL3_CLK_FREQ = 12.727e6;
+    localparam OPL3_CLK_FREQ = CLK_FREQ;
     localparam OPL3_CLK_HALF_PERIOD = 1/real'(OPL3_CLK_FREQ)*1000e6/2;
 
     localparam GATE_DELAY = 1; // in ns
@@ -60,7 +60,6 @@ module opl3_tb
     bit [1:0] address = 0;
     bit [REG_FILE_DATA_WIDTH-1:0] din = 0;
     logic [REG_FILE_DATA_WIDTH-1:0] dout;
-    logic ack_host_wr; // host needs to hold writes for clock domain crossing
     logic sample_valid;
     logic signed [DAC_OUTPUT_WIDTH-1:0] sample_l;
     logic signed [DAC_OUTPUT_WIDTH-1:0] sample_r;
@@ -86,7 +85,6 @@ module opl3_tb
             default input #1step;
             default output #GATE_DELAY;
             input dout;
-            input ack_host_wr; // host needs to hold writes for clock domain crossing
             output ic_n; // clk_host reset
             output cs_n;
             output rd_n;
@@ -95,6 +93,21 @@ module opl3_tb
             output din;
         endclocking
 
+        task opl3_read (
+            output [REG_FILE_DATA_WIDTH-1:0] value
+        );
+            bit [1:0] opl3_address;
+
+            cb.address <= 'b00;
+            cb.cs_n <= 0;
+            cb.rd_n <= 0;
+            ##1;
+            value <= cb.dout;
+            cb.cs_n <= 1;
+            cb.rd_n <= 1;
+            ##1;
+        endtask
+
         task opl3_write (
             input [REG_FILE_DATA_WIDTH-1:0] register,
             input [REG_FILE_DATA_WIDTH-1:0] data_in,
@@ -104,44 +117,74 @@ module opl3_tb
             bit [REG_FILE_DATA_WIDTH-1:0] data;
 
             // write OPL3 address
-            address = bank ? 'b10 : 'b00;
+            opl3_address = bank ? 'b10 : 'b00;
             data = register;
-            cb.address <= address;
+            cb.address <= opl3_address;
             cb.din <= data;
             cb.cs_n <= 0;
             cb.wr_n <= 0;
-            while (!cb.ack_host_wr)
-                ##1;
+            ##1;
             cb.cs_n <= 1;
             cb.wr_n <= 1;
-            ##15; // need time for opl3 cs to deassert internally due to slow clock speed and synchronizer delay
+            cb.din <= 0;
+            ##1;
+            for (int i = 0; i < 6; ++i)
+                opl3_read(data);
 
             // write OPL3 data
-            address = 'b01;
+            opl3_address = 'b01;
             data = data_in;
-            cb.address <= address;
+            cb.address <= opl3_address;
             cb.din <= data;
             cb.cs_n <= 0;
             cb.wr_n <= 0;
-            while (!cb.ack_host_wr)
-                ##1;
+            ##1;
             cb.cs_n <= 1;
             cb.wr_n <= 1;
-            ##15;
+            cb.din <= 0;
+            ##1;
+            for (int i = 0; i < 36; ++i)
+                opl3_read(data);
+
+            $display("Wrote 0x%0x to register 0x%0x, bank %0x", data_in, register, bank);
+        endtask
+
+        task detect_opl3();
+            bit [REG_FILE_DATA_WIDTH-1:0] stat1, stat2, dummy;
+
+            opl3_write('h04, 'h60, 'b0); // mask timers 1 and 2
+            opl3_write('h04, 'h80, 'b0); // rst_irq, unmask timers
+            opl3_read(stat1);
+            opl3_write('h02, 'hff, 'b0); // set timer 1 to max value
+            opl3_write('h04, 'h21, 'b0); // mask timer 2, start timer 1
+            for (int i = 0; i < 4000; ++i)
+                opl3_read(dummy);
+            opl3_read(stat2);
+            opl3_write('h04, 'h60, 'b0);
+            opl3_write('h04, 'h80, 'b0);
+            if ((stat1 & 'he0) == 0 && (stat2 & 'he0) == 'hc0)
+                $display("OPL3 detected!");
+            else
+                $error("OPL3 not detected...");
+        endtask
+
+        task reset_opl3();
+            for (int i = 'h20; i < 'hff; ++i) begin
+                if ((i & 'he0) == 'h80)
+                    opl3_write(i, 'h0f, 0);
+                else
+                    opl3_write(i, 'h00, 0);
+            end
         endtask
 
         initial begin
             cb.ic_n <= 0;
             ##100;
             cb.ic_n <= 1;
-            ##10;
-            opl3_write('h2, 245, 0); // set timer1 reg
-            opl3_write('h4, 8'b01000001, 0); // start and unmask timer1
-            #805000; // wait
-            assert(!irq_n);
-            opl3_write('h4, 8'b11000000, 0); // rst irq, stop timer1
-            opl3_write('h4, 8'b01000001, 0); // start and unmask timer1
-            #10000;
+            ##100;
+            detect_opl3();
+            reset_opl3();
+            detect_opl3();
         end
     endprogram
 endmodule
diff --git a/fpga/modules/top_level/src/opl3.sv b/fpga/modules/top_level/src/opl3.sv
index 15a2070..24afe1d 100755
--- a/fpga/modules/top_level/src/opl3.sv
+++ b/fpga/modules/top_level/src/opl3.sv
@@ -53,7 +53,6 @@ module opl3
     input wire [1:0] address,
     input wire [REG_FILE_DATA_WIDTH-1:0] din,
     output logic [REG_FILE_DATA_WIDTH-1:0] dout,
-    output logic ack_host_wr, // host needs to hold writes for clock domain crossing
     output logic sample_valid,
     output logic signed [DAC_OUTPUT_WIDTH-1:0] sample_l,
     output logic signed [DAC_OUTPUT_WIDTH-1:0] sample_r,
@@ -62,14 +61,9 @@ module opl3
 );
     logic reset;
     logic sample_clk_en;
-
     opl3_reg_wr_t opl3_reg_wr;
-    logic signed [SAMPLE_WIDTH-1:0] channel_a;
-    logic signed [SAMPLE_WIDTH-1:0] channel_b;
-    logic signed [SAMPLE_WIDTH-1:0] channel_c;
-    logic signed [SAMPLE_WIDTH-1:0] channel_d;
     logic [REG_FILE_DATA_WIDTH-1:0] status;
-    logic channel_valid;
+    logic force_timer_overflow;
 
     reset_sync reset_sync (
         .clk,