/*
|
/*
|
SQmusic
|
SQmusic
|
Music synthetiser compatible with AY-3-8910 software compatible
|
Music synthetiser compatible with AY-3-8910 software compatible
|
Version 0.1, tested on simulation only with Capcom's 1942
|
Version 0.1, tested on simulation only with Capcom's 1942
|
|
|
(c) Jose Tejada Gomez, 9th May 2013
|
(c) Jose Tejada Gomez, 9th May 2013
|
You can use this file following the GNU GENERAL PUBLIC LICENSE version 3
|
You can use this file following the GNU GENERAL PUBLIC LICENSE version 3
|
Read the details of the license in:
|
Read the details of the license in:
|
http://www.gnu.org/licenses/gpl.txt
|
http://www.gnu.org/licenses/gpl.txt
|
|
|
Send comments to: jose.tejada@ieee.org
|
Send comments to: jose.tejada@ieee.org
|
|
|
*/
|
*/
|
|
|
/* Capcom arcade boards like 1942 use two memory locations to
|
/* Capcom arcade boards like 1942 use two memory locations to
|
communicate with the AY-3-8910 (or compatible) chip. This small code
|
communicate with the AY-3-8910 (or compatible) chip. This small code
|
provides a 2-byte memory map as expected by Capcom games
|
provides a 2-byte memory map as expected by Capcom games
|
*/
|
*/
|
`timescale 1ns / 1ps
|
`timescale 1ns / 1ps
|
module AY_3_8910_capcom(
|
module AY_3_8910_capcom
|
|
#( parameter dump_writes=0, parameter id=0 )
|
|
(
|
input reset_n,
|
input reset_n,
|
input clk, // CPU clock
|
input clk, // CPU clock
|
input sound_clk, // normally slower than the CPU clock
|
input sound_clk, // normally slower than the CPU clock
|
input [7:0] din,
|
input [7:0] din,
|
input adr,
|
input adr,
|
input wr_n, // write
|
input wr_n, // write
|
input cs_n, // chip select
|
input cs_n, // chip select
|
output [3:0]A,B,C // channel outputs
|
output [3:0]A,B,C // channel outputs
|
);
|
);
|
|
|
reg [7:0] latches[1:0];
|
reg [7:0] latches[1:0];
|
reg core_wr;
|
reg core_wr;
|
wire sample = clk & ~cs_n & ~wr_n;
|
wire sample = clk & ~cs_n & ~wr_n;
|
reg count;
|
reg count;
|
|
|
always @(posedge sound_clk or negedge reset_n) begin
|
always @(posedge sound_clk or negedge reset_n) begin
|
if(!reset_n) begin
|
if(!reset_n) begin
|
count=0;
|
count=0;
|
end
|
end
|
else begin
|
else begin
|
if( !count && core_wr) count=1;
|
if( !count && core_wr) count=1;
|
else if( core_wr ) begin
|
else if( core_wr ) begin
|
count=0;
|
count=0;
|
core_wr=0;
|
core_wr=0;
|
end
|
end
|
end
|
end
|
end
|
end
|
|
|
always @(posedge sample or negedge reset_n) begin
|
always @(posedge sample or negedge reset_n) begin
|
if(!reset_n) begin
|
if(!reset_n) begin
|
latches[0]=0;
|
latches[0]=0;
|
latches[1]=0;
|
latches[1]=0;
|
end
|
end
|
else begin
|
else begin
|
latches[adr] = din;
|
latches[adr] = din;
|
if(adr) core_wr=1;
|
if(adr) core_wr=1;
|
end
|
end
|
end
|
end
|
|
|
SQMUSIC core( .reset_n(reset_n), .clk(sound_clk), .data_in(latches[1]),
|
SQMUSIC #(dump_writes, id) core( .reset_n(reset_n), .clk(sound_clk), .data_in(latches[1]),
|
.adr( latches[0][3:0] ), .rd(1'b0), .wr(core_wr), .A(A), .B(B), .C(C) );
|
.adr( latches[0][3:0] ), .rd(1'b0), .wr(core_wr), .A(A), .B(B), .C(C) );
|
endmodule
|
endmodule
|
|
|
/* The AY core does
|
/* The AY core does
|
*/
|
*/
|
module SQMUSIC( // pins are not multiplexed
|
module SQMUSIC
|
|
#( parameter dump_writes=0, parameter id=0 ) // set to 1 to dump register writes
|
|
( // note that input ports are not multiplexed
|
input reset_n,
|
input reset_n,
|
input clk,
|
input clk,
|
input [7:0] data_in,
|
input [7:0] data_in,
|
output reg [7:0] data_out, // read functionality not implemented yet
|
output reg [7:0] data_out,
|
input [3:0] adr,
|
input [3:0] adr,
|
input rd, // read
|
input rd, // read
|
input wr, // write
|
input wr, // write
|
output [3:0]A,B,C // channel outputs
|
output [3:0]A,B,C // channel outputs
|
);
|
);
|
|
|
reg [7:0] regarray[15:0];
|
reg [7:0] regarray[15:0];
|
reg [3:0] clkdiv16;
|
reg [3:0] clkdiv16;
|
|
|
wire [3:0] envelope;
|
wire [3:0] envelope;
|
wire [2:0] sqwave;
|
wire [2:0] sqwave;
|
wire noise, envclk;
|
wire noise, envclk;
|
wire Amix = (noise|regarray[7][3]) ^ (sqwave[0]|regarray[7][0]);
|
wire Amix = (noise|regarray[7][3]) ^ (sqwave[0]|regarray[7][0]);
|
wire Bmix = (noise|regarray[7][4]) ^ (sqwave[1]|regarray[7][1]);
|
wire Bmix = (noise|regarray[7][4]) ^ (sqwave[1]|regarray[7][1]);
|
wire Cmix = (noise|regarray[7][5]) ^ (sqwave[2]|regarray[7][2]);
|
wire Cmix = (noise|regarray[7][5]) ^ (sqwave[2]|regarray[7][2]);
|
|
|
// internal modules operate at clk/16
|
// internal modules operate at clk/16
|
SQM_CLK_DIVIDER #(12) chA( .clk(clkdiv16[3]), .reset_n(reset_n),
|
SQM_CLK_DIVIDER #(12) chA( .clk(clkdiv16[3]), .reset_n(reset_n),
|
.period({regarray[1][3:0], regarray[0][7:0] }), .div(sqwave[0]) );
|
.period({regarray[1][3:0], regarray[0][7:0] }), .div(sqwave[0]) );
|
SQM_CLK_DIVIDER #(12) chB( .clk(clkdiv16[3]), .reset_n(reset_n),
|
SQM_CLK_DIVIDER #(12) chB( .clk(clkdiv16[3]), .reset_n(reset_n),
|
.period({regarray[3][3:0], regarray[2][7:0] }), .div(sqwave[1]) );
|
.period({regarray[3][3:0], regarray[2][7:0] }), .div(sqwave[1]) );
|
SQM_CLK_DIVIDER #(12) chC( .clk(clkdiv16[3]), .reset_n(reset_n),
|
SQM_CLK_DIVIDER #(12) chC( .clk(clkdiv16[3]), .reset_n(reset_n),
|
.period({regarray[5][3:0], regarray[4][7:0] }), .div(sqwave[2]) );
|
.period({regarray[5][3:0], regarray[4][7:0] }), .div(sqwave[2]) );
|
|
|
// the noise uses a x2 faster clock in order to produce a frequency
|
// the noise uses a x2 faster clock in order to produce a frequency
|
// of Fclk/16 when period is 1
|
// of Fclk/16 when period is 1
|
SQM_NOISE ng( .clk(clkdiv16[3]), .reset_n(reset_n),
|
SQM_NOISE ng( .clk(clkdiv16[3]), .reset_n(reset_n),
|
.period(regarray[6][4:0]), .noise(noise) );
|
.period(regarray[6][4:0]), .noise(noise) );
|
// envelope generator
|
// envelope generator
|
SQM_CLK_DIVIDER #(16) envclkdiv( .clk(clkdiv16[2]), .reset_n(reset_n),
|
SQM_CLK_DIVIDER #(16) envclkdiv( .clk(clkdiv16[2]), .reset_n(reset_n),
|
.period({regarray[14],regarray[13]}), .div(envclk) );
|
.period({regarray[14],regarray[13]}), .div(envclk) );
|
SQM_ENVELOPE env( .clk(envclk),.ctrl(regarray[15][3:0]),
|
SQM_ENVELOPE env( .clk(envclk),.ctrl(regarray[15][3:0]),
|
.gain(envelope), .reset_n(reset_n) );
|
.gain(envelope), .reset_n(reset_n) );
|
|
|
assign A=regarray[10][4]? envelope&{4{Amix}} : regarray[10][3:0]&{4{Amix}};
|
assign A=regarray[10][4]? envelope&{4{Amix}} : regarray[10][3:0]&{4{Amix}};
|
assign B=regarray[11][4]? envelope&{4{Bmix}} : regarray[10][3:0]&{4{Bmix}};
|
assign B=regarray[11][4]? envelope&{4{Bmix}} : regarray[10][3:0]&{4{Bmix}};
|
assign C=regarray[12][4]? envelope&{4{Cmix}} : regarray[10][3:0]&{4{Cmix}};
|
assign C=regarray[12][4]? envelope&{4{Cmix}} : regarray[10][3:0]&{4{Cmix}};
|
|
|
integer aux;
|
integer aux;
|
|
|
// 16-count divider
|
// 16-count divider
|
always @(posedge clk or reset_n) begin
|
always @(posedge clk or reset_n) begin
|
if( !reset_n)
|
if( !reset_n)
|
clkdiv16=0;
|
clkdiv16=0;
|
else
|
else
|
clkdiv16<=clkdiv16+1;
|
clkdiv16<=clkdiv16+1;
|
end
|
end
|
|
|
always @(posedge clk or reset_n) begin
|
always @(posedge clk or reset_n) begin
|
if( !reset_n ) begin
|
if( !reset_n ) begin
|
data_out=0;
|
data_out=0;
|
for(aux=0;aux<=15;aux=aux+1) regarray[aux]=0;
|
for(aux=0;aux<=15;aux=aux+1) regarray[aux]=0;
|
end
|
end
|
else begin
|
else begin
|
if( rd )
|
if( rd )
|
data_out=regarray[ adr ];
|
data_out=regarray[ adr ];
|
else if( wr ) regarray[adr]=data_in;
|
else if( wr ) begin
|
|
regarray[adr]=data_in;
|
|
if( dump_writes ) begin
|
|
$display("#%d, %t, %d, %d", id, $realtime, adr, data_in );
|
|
end
|
|
end
|
end
|
end
|
end
|
end
|
|
|
endmodule
|
endmodule
|
|
|
module SQM_CLK_DIVIDER(
|
module SQM_CLK_DIVIDER(
|
clk, // this is the divided down clock from the core
|
clk, // this is the divided down clock from the core
|
reset_n,
|
reset_n,
|
period,
|
period,
|
div
|
div
|
);
|
);
|
|
|
parameter bw=12;
|
parameter bw=12;
|
input clk; // this is the divided down clock from the core
|
input clk; // this is the divided down clock from the core
|
input reset_n;
|
input reset_n;
|
input [bw-1:0]period;
|
input [bw-1:0]period;
|
output div;
|
output div;
|
|
|
reg [bw-1:0]count;
|
reg [bw-1:0]count;
|
reg clkdiv;
|
reg clkdiv;
|
|
|
initial clkdiv=0;
|
initial clkdiv=0;
|
|
|
assign div = period==1 ? clk : clkdiv;
|
assign div = period==1 ? clk : clkdiv;
|
|
|
always @(posedge clk or reset_n) begin
|
always @(posedge clk or reset_n) begin
|
if( !reset_n) begin
|
if( !reset_n) begin
|
count=0;
|
count=0;
|
clkdiv=0;
|
clkdiv=0;
|
end
|
end
|
else begin
|
else begin
|
if( period==0 ) begin
|
if( period==0 ) begin
|
clkdiv<=0;
|
clkdiv<=0;
|
count<=0;
|
count<=0;
|
end
|
end
|
else if( count >= period ) begin
|
else if( count >= period ) begin
|
count <= 0;
|
count <= 0;
|
clkdiv = ~clkdiv;
|
clkdiv = ~clkdiv;
|
end
|
end
|
else count <= count+1;
|
else count <= count+1;
|
end
|
end
|
end
|
end
|
endmodule
|
endmodule
|
|
|
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
module SQM_NOISE(
|
module SQM_NOISE(
|
input clk, // this is the divided down clock from the core
|
input clk, // this is the divided down clock from the core
|
input reset_n,
|
input reset_n,
|
input [4:0]period,
|
input [4:0]period,
|
output noise
|
output noise
|
);
|
);
|
|
|
reg [5:0]count;
|
reg [5:0]count;
|
reg [16:0]poly17;
|
reg [16:0]poly17;
|
wire poly17_zero = poly17==0;
|
wire poly17_zero = poly17==0;
|
assign noise=poly17[16];
|
assign noise=poly17[16];
|
wire noise_clk;
|
wire noise_clk;
|
|
|
always @(posedge noise_clk or reset_n) begin
|
always @(posedge noise_clk or reset_n) begin
|
if( !reset_n) begin
|
if( !reset_n) begin
|
poly17=0;
|
poly17=0;
|
end
|
end
|
else begin
|
else begin
|
poly17={ poly17[0] ^ poly17[2] ^ poly17_zero, poly17[16:1] };
|
poly17={ poly17[0] ^ poly17[2] ^ poly17_zero, poly17[16:1] };
|
end
|
end
|
end
|
end
|
|
|
SQM_CLK_DIVIDER #(5) ndiv( .clk(clk), .reset_n(reset_n),
|
SQM_CLK_DIVIDER #(5) ndiv( .clk(clk), .reset_n(reset_n),
|
.period(period), .div(noise_clk) );
|
.period(period), .div(noise_clk) );
|
endmodule
|
endmodule
|
|
|
////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////
|
module SQM_ENVELOPE(
|
module SQM_ENVELOPE(
|
input clk, // this is the divided down clock from the core
|
input clk, // this is the divided down clock from the core
|
input reset_n,
|
input reset_n,
|
input [3:0]ctrl,
|
input [3:0]ctrl,
|
output reg [3:0]gain
|
output reg [3:0]gain
|
);
|
);
|
|
|
reg dir; // direction
|
reg dir; // direction
|
reg stop;
|
reg stop;
|
reg [3:0]prev_ctrl; // last control orders
|
reg [3:0]prev_ctrl; // last control orders
|
|
|
always @(posedge clk or reset_n) begin
|
always @(posedge clk or reset_n) begin
|
if( !reset_n) begin
|
if( !reset_n) begin
|
gain=4'hF;
|
gain=4'hF;
|
dir=0;
|
dir=0;
|
prev_ctrl=0;
|
prev_ctrl=0;
|
stop=1;
|
stop=1;
|
end
|
end
|
else begin
|
else begin
|
if (ctrl!=prev_ctrl) begin
|
if (ctrl!=prev_ctrl) begin
|
prev_ctrl<=ctrl;
|
prev_ctrl<=ctrl;
|
if( ctrl[2] ) begin
|
if( ctrl[2] ) begin
|
gain<=0;
|
gain<=0;
|
dir<=1;
|
dir<=1;
|
stop<=0;
|
stop<=0;
|
end
|
end
|
else begin
|
else begin
|
gain<=4'hF;
|
gain<=4'hF;
|
dir<=0;
|
dir<=0;
|
stop<=0;
|
stop<=0;
|
end
|
end
|
end
|
end
|
else begin
|
else begin
|
if (!stop) begin
|
if (!stop) begin
|
if( !prev_ctrl[3] && ((gain==0&&!dir) || (gain==4'hF&&dir))) begin
|
if( !prev_ctrl[3] && ((gain==0&&!dir) || (gain==4'hF&&dir))) begin
|
stop<=1;
|
stop<=1;
|
gain<=0;
|
gain<=0;
|
end
|
end
|
else begin
|
else begin
|
if( prev_ctrl[0] && ( (gain==0&&!dir) || (gain==4'hF&&dir))) begin // HOLD
|
if( prev_ctrl[0] && ( (gain==0&&!dir) || (gain==4'hF&&dir))) begin // HOLD
|
stop<=1;
|
stop<=1;
|
gain <= prev_ctrl[1]? ~gain : gain;
|
gain <= prev_ctrl[1]? ~gain : gain;
|
end
|
end
|
else begin
|
else begin
|
gain <= dir ? gain+1 : gain-1;
|
gain <= dir ? gain+1 : gain-1;
|
if( prev_ctrl[1:0]==2'b10 && ( (gain==1&&!dir) || (gain==4'hE&&dir))) begin // ALTERNATE
|
if( prev_ctrl[1:0]==2'b10 && ( (gain==1&&!dir) || (gain==4'hE&&dir))) begin // ALTERNATE
|
dir <= ~dir;
|
dir <= ~dir;
|
end
|
end
|
end
|
end
|
end
|
end
|
end
|
end
|
end
|
end
|
end
|
end
|
end
|
end
|
endmodule
|
endmodule
|
|
|