Skip to content

Commit

Permalink
Use async fifo for host_if, add trick_sw_detection logic (#31)
Browse files Browse the repository at this point in the history
* fix timers, add detect opl3 testbench

* comments

* convert host_if to fifo design

* added trick_sw_detection logic

* added some color to comments
  • Loading branch information
gtaylormb authored Apr 13, 2024
1 parent 7326bb4 commit 5adbe31
Show file tree
Hide file tree
Showing 12 changed files with 1,087 additions and 132 deletions.
4 changes: 3 additions & 1 deletion fpga/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion fpga/modules/clks/src/reset_sync.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
119 changes: 40 additions & 79 deletions fpga/modules/host_if/src/host_if.sv
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
145 changes: 145 additions & 0 deletions fpga/modules/host_if/src/trick_sw_detection.sv
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*******************************************************************************
# +html+<pre>
#
# 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 <[email protected]>
#
# 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 <http://www.gnu.org/licenses/>.
#
# Original Java Code:
# Copyright (C) 2008 Robson Cozendey <[email protected]>
#
# Original C++ Code:
# Copyright (C) 2012 Steffen Ohrendorf <[email protected]>
#
# 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
Loading

0 comments on commit 5adbe31

Please sign in to comment.