feat(apu): implement all apu i/o writes
This commit is contained in:
parent
13710a3236
commit
1d163fa56f
122
src/core/apu.zig
122
src/core/apu.zig
|
@ -12,9 +12,8 @@ const Noise = @import("apu/Noise.zig");
|
||||||
|
|
||||||
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
|
const SoundFifo = std.fifo.LinearFifo(u8, .{ .Static = 0x20 });
|
||||||
|
|
||||||
const setHi = util.setHi;
|
const getHalf = util.shift;
|
||||||
const setLo = util.setLo;
|
const setHalf = util.setHalf;
|
||||||
const shift = util.shift;
|
|
||||||
const intToBytes = util.intToBytes;
|
const intToBytes = util.intToBytes;
|
||||||
|
|
||||||
const log = std.log.scoped(.APU);
|
const log = std.log.scoped(.APU);
|
||||||
|
@ -23,10 +22,10 @@ pub const host_rate = @import("../platform.zig").sample_rate;
|
||||||
pub const host_format = @import("../platform.zig").sample_format;
|
pub const host_format = @import("../platform.zig").sample_format;
|
||||||
|
|
||||||
pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
|
pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
|
||||||
const byte = @truncate(u8, addr);
|
const byte_addr = @truncate(u8, addr);
|
||||||
|
|
||||||
return switch (T) {
|
return switch (T) {
|
||||||
u32 => switch (byte) {
|
u32 => switch (byte_addr) {
|
||||||
0x60 => @as(T, apu.ch1.sound1CntH()) << 16 | apu.ch1.sound1CntL(),
|
0x60 => @as(T, apu.ch1.sound1CntH()) << 16 | apu.ch1.sound1CntL(),
|
||||||
0x64 => apu.ch1.sound1CntX(),
|
0x64 => apu.ch1.sound1CntX(),
|
||||||
0x68 => apu.ch2.sound2CntL(),
|
0x68 => apu.ch2.sound2CntL(),
|
||||||
|
@ -39,12 +38,12 @@ pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
|
||||||
0x84 => apu.soundCntX(),
|
0x84 => apu.soundCntX(),
|
||||||
0x88 => apu.bias.raw, // SOUNDBIAS, high is unused
|
0x88 => apu.bias.raw, // SOUNDBIAS, high is unused
|
||||||
0x8C => null,
|
0x8C => null,
|
||||||
0x90, 0x94, 0x8, 0x9C => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
|
0x90, 0x94, 0x98, 0x9C => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
|
||||||
0xA0 => null, // FIFO_A
|
0xA0 => null, // FIFO_A
|
||||||
0xA4 => null, // FIFO_B
|
0xA4 => null, // FIFO_B
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
||||||
},
|
},
|
||||||
u16 => switch (byte) {
|
u16 => switch (byte_addr) {
|
||||||
0x60 => apu.ch1.sound1CntL(),
|
0x60 => apu.ch1.sound1CntL(),
|
||||||
0x62 => apu.ch1.sound1CntH(),
|
0x62 => apu.ch1.sound1CntH(),
|
||||||
0x64 => apu.ch1.sound1CntX(),
|
0x64 => apu.ch1.sound1CntX(),
|
||||||
|
@ -73,28 +72,28 @@ pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
|
||||||
0xA4, 0xA6 => null, // FIFO_B
|
0xA4, 0xA6 => null, // FIFO_B
|
||||||
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
else => util.io.read.err(T, log, "unaligned {} read from 0x{X:0>8}", .{ T, addr }),
|
||||||
},
|
},
|
||||||
u8 => switch (byte) {
|
u8 => switch (byte_addr) {
|
||||||
0x60, 0x61 => @truncate(T, @as(u16, apu.ch1.sound1CntL()) >> shift(byte)),
|
0x60, 0x61 => @truncate(T, @as(u16, apu.ch1.sound1CntL()) >> getHalf(byte_addr)),
|
||||||
0x62, 0x63 => @truncate(T, apu.ch1.sound1CntH() >> shift(byte)),
|
0x62, 0x63 => @truncate(T, apu.ch1.sound1CntH() >> getHalf(byte_addr)),
|
||||||
0x64, 0x65 => @truncate(T, apu.ch1.sound1CntX() >> shift(byte)),
|
0x64, 0x65 => @truncate(T, apu.ch1.sound1CntX() >> getHalf(byte_addr)),
|
||||||
0x66, 0x67 => 0x00, // assuming behaviour is identical to that of 16-bit reads
|
0x66, 0x67 => 0x00, // assuming behaviour is identical to that of 16-bit reads
|
||||||
0x68, 0x69 => @truncate(T, apu.ch2.sound2CntL() >> shift(byte)),
|
0x68, 0x69 => @truncate(T, apu.ch2.sound2CntL() >> getHalf(byte_addr)),
|
||||||
0x6A, 0x6B => 0x00,
|
0x6A, 0x6B => 0x00,
|
||||||
0x6C, 0x6D => @truncate(T, apu.ch2.sound2CntH() >> shift(byte)),
|
0x6C, 0x6D => @truncate(T, apu.ch2.sound2CntH() >> getHalf(byte_addr)),
|
||||||
0x6E, 0x6F => 0x00,
|
0x6E, 0x6F => 0x00,
|
||||||
0x70, 0x71 => @truncate(T, @as(u16, apu.ch3.sound3CntL()) >> shift(byte)), // SOUND3CNT_L
|
0x70, 0x71 => @truncate(T, @as(u16, apu.ch3.sound3CntL()) >> getHalf(byte_addr)), // SOUND3CNT_L
|
||||||
0x72, 0x73 => @truncate(T, apu.ch3.sound3CntH() >> shift(byte)),
|
0x72, 0x73 => @truncate(T, apu.ch3.sound3CntH() >> getHalf(byte_addr)),
|
||||||
0x74, 0x75 => @truncate(T, apu.ch3.sound3CntX() >> shift(byte)), // SOUND3CNT_L
|
0x74, 0x75 => @truncate(T, apu.ch3.sound3CntX() >> getHalf(byte_addr)), // SOUND3CNT_L
|
||||||
0x76, 0x77 => 0x00,
|
0x76, 0x77 => 0x00,
|
||||||
0x78, 0x79 => @truncate(T, apu.ch4.sound4CntL() >> shift(byte)),
|
0x78, 0x79 => @truncate(T, apu.ch4.sound4CntL() >> getHalf(byte_addr)),
|
||||||
0x7A, 0x7B => 0x00,
|
0x7A, 0x7B => 0x00,
|
||||||
0x7C, 0x7D => @truncate(T, apu.ch4.sound4CntH() >> shift(byte)),
|
0x7C, 0x7D => @truncate(T, apu.ch4.sound4CntH() >> getHalf(byte_addr)),
|
||||||
0x7E, 0x7F => 0x00,
|
0x7E, 0x7F => 0x00,
|
||||||
0x80, 0x81 => @truncate(T, apu.soundCntL() >> shift(byte)), // SOUNDCNT_L
|
0x80, 0x81 => @truncate(T, apu.soundCntL() >> getHalf(byte_addr)), // SOUNDCNT_L
|
||||||
0x82, 0x83 => @truncate(T, apu.soundCntH() >> shift(byte)), // SOUNDCNT_H
|
0x82, 0x83 => @truncate(T, apu.soundCntH() >> getHalf(byte_addr)), // SOUNDCNT_H
|
||||||
0x84, 0x85 => @truncate(T, @as(u16, apu.soundCntX()) >> shift(byte)),
|
0x84, 0x85 => @truncate(T, @as(u16, apu.soundCntX()) >> getHalf(byte_addr)),
|
||||||
0x86, 0x87 => 0x00,
|
0x86, 0x87 => 0x00,
|
||||||
0x88, 0x89 => @truncate(T, apu.bias.raw >> shift(byte)), // SOUNDBIAS
|
0x88, 0x89 => @truncate(T, apu.bias.raw >> getHalf(byte_addr)), // SOUNDBIAS
|
||||||
0x8A, 0x8B => 0x00,
|
0x8A, 0x8B => 0x00,
|
||||||
0x8C...0x8F => null,
|
0x8C...0x8F => null,
|
||||||
0x90...0x9F => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
|
0x90...0x9F => apu.ch3.wave_dev.read(T, apu.ch3.select, addr),
|
||||||
|
@ -107,79 +106,107 @@ pub fn read(comptime T: type, apu: *const Apu, addr: u32) ?T {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
|
pub fn write(comptime T: type, apu: *Apu, addr: u32, value: T) void {
|
||||||
const byte = @truncate(u8, addr);
|
const byte_addr = @truncate(u8, addr);
|
||||||
|
|
||||||
switch (T) {
|
switch (T) {
|
||||||
u32 => switch (byte) {
|
u32 => switch (byte_addr) {
|
||||||
0x60 => apu.ch1.setSound1Cnt(value),
|
0x60 => apu.ch1.setSound1Cnt(value),
|
||||||
0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
|
0x64 => apu.ch1.setSound1CntX(&apu.fs, @truncate(u16, value)),
|
||||||
|
|
||||||
0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)),
|
0x68 => apu.ch2.setSound2CntL(@truncate(u16, value)),
|
||||||
0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)),
|
0x6C => apu.ch2.setSound2CntH(&apu.fs, @truncate(u16, value)),
|
||||||
|
|
||||||
0x70 => apu.ch3.setSound3Cnt(value),
|
0x70 => apu.ch3.setSound3Cnt(value),
|
||||||
0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)),
|
0x74 => apu.ch3.setSound3CntX(&apu.fs, @truncate(u16, value)),
|
||||||
|
|
||||||
0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)),
|
0x78 => apu.ch4.setSound4CntL(@truncate(u16, value)),
|
||||||
0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)),
|
0x7C => apu.ch4.setSound4CntH(&apu.fs, @truncate(u16, value)),
|
||||||
|
|
||||||
0x80 => apu.setSoundCnt(value),
|
0x80 => apu.setSoundCnt(value),
|
||||||
// WAVE_RAM
|
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
|
||||||
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
0x88 => apu.bias.raw = @truncate(u16, value),
|
||||||
|
0x8C => {},
|
||||||
|
|
||||||
|
0x90, 0x94, 0x98, 0x9C => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
||||||
0xA0 => apu.chA.push(value), // FIFO_A
|
0xA0 => apu.chA.push(value), // FIFO_A
|
||||||
0xA4 => apu.chB.push(value), // FIFO_B
|
0xA4 => apu.chB.push(value), // FIFO_B
|
||||||
else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
|
else => util.io.write.undef(log, "Tried to write 0x{X:0>8}{} to 0x{X:0>8}", .{ value, T, addr }),
|
||||||
},
|
},
|
||||||
u16 => switch (byte) {
|
u16 => switch (byte_addr) {
|
||||||
0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L
|
0x60 => apu.ch1.setSound1CntL(@truncate(u8, value)), // SOUND1CNT_L
|
||||||
0x62 => apu.ch1.setSound1CntH(value),
|
0x62 => apu.ch1.setSound1CntH(value),
|
||||||
0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
|
0x64 => apu.ch1.setSound1CntX(&apu.fs, value),
|
||||||
|
0x66 => {},
|
||||||
|
|
||||||
0x68 => apu.ch2.setSound2CntL(value),
|
0x68 => apu.ch2.setSound2CntL(value),
|
||||||
|
0x6A => {},
|
||||||
0x6C => apu.ch2.setSound2CntH(&apu.fs, value),
|
0x6C => apu.ch2.setSound2CntH(&apu.fs, value),
|
||||||
|
0x6E => {},
|
||||||
|
|
||||||
0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)),
|
0x70 => apu.ch3.setSound3CntL(@truncate(u8, value)),
|
||||||
0x72 => apu.ch3.setSound3CntH(value),
|
0x72 => apu.ch3.setSound3CntH(value),
|
||||||
0x74 => apu.ch3.setSound3CntX(&apu.fs, value),
|
0x74 => apu.ch3.setSound3CntX(&apu.fs, value),
|
||||||
|
0x76 => {},
|
||||||
|
|
||||||
0x78 => apu.ch4.setSound4CntL(value),
|
0x78 => apu.ch4.setSound4CntL(value),
|
||||||
|
0x7A => {},
|
||||||
0x7C => apu.ch4.setSound4CntH(&apu.fs, value),
|
0x7C => apu.ch4.setSound4CntH(&apu.fs, value),
|
||||||
|
0x7E => {},
|
||||||
|
|
||||||
0x80 => apu.psg_cnt.raw = value, // SOUNDCNT_L
|
0x80 => apu.setSoundCntL(value),
|
||||||
0x82 => apu.setSoundCntH(value),
|
0x82 => apu.setSoundCntH(value),
|
||||||
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
|
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
|
||||||
|
0x86 => {},
|
||||||
0x88 => apu.bias.raw = value, // SOUNDBIAS
|
0x88 => apu.bias.raw = value, // SOUNDBIAS
|
||||||
// WAVE_RAM
|
0x8A, 0x8C, 0x8E => {},
|
||||||
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
|
||||||
|
0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
||||||
|
0xA0, 0xA2 => log.err("Tried to write 0x{X:0>4}{} to FIFO_A", .{ value, T }),
|
||||||
|
0xA4, 0xA6 => log.err("Tried to write 0x{X:0>4}{} to FIFO_B", .{ value, T }),
|
||||||
else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
|
else => util.io.write.undef(log, "Tried to write 0x{X:0>4}{} to 0x{X:0>8}", .{ value, T, addr }),
|
||||||
},
|
},
|
||||||
u8 => switch (byte) {
|
u8 => switch (byte_addr) {
|
||||||
0x60 => apu.ch1.setSound1CntL(value),
|
0x60 => apu.ch1.setSound1CntL(value),
|
||||||
|
0x61 => {},
|
||||||
0x62 => apu.ch1.setNr11(value),
|
0x62 => apu.ch1.setNr11(value),
|
||||||
0x63 => apu.ch1.setNr12(value),
|
0x63 => apu.ch1.setNr12(value),
|
||||||
0x64 => apu.ch1.setNr13(value),
|
0x64 => apu.ch1.setNr13(value),
|
||||||
0x65 => apu.ch1.setNr14(&apu.fs, value),
|
0x65 => apu.ch1.setNr14(&apu.fs, value),
|
||||||
|
0x66, 0x67 => {},
|
||||||
|
|
||||||
0x68 => apu.ch2.setNr21(value),
|
0x68 => apu.ch2.setNr21(value),
|
||||||
0x69 => apu.ch2.setNr22(value),
|
0x69 => apu.ch2.setNr22(value),
|
||||||
|
0x6A, 0x6B => {},
|
||||||
0x6C => apu.ch2.setNr23(value),
|
0x6C => apu.ch2.setNr23(value),
|
||||||
0x6D => apu.ch2.setNr24(&apu.fs, value),
|
0x6D => apu.ch2.setNr24(&apu.fs, value),
|
||||||
|
0x6E, 0x6F => {},
|
||||||
|
|
||||||
0x70 => apu.ch3.setSound3CntL(value), // NR30
|
0x70 => apu.ch3.setSound3CntL(value), // NR30
|
||||||
|
0x71 => {},
|
||||||
0x72 => apu.ch3.setNr31(value),
|
0x72 => apu.ch3.setNr31(value),
|
||||||
0x73 => apu.ch3.vol.raw = value, // NR32
|
0x73 => apu.ch3.vol.raw = value, // NR32
|
||||||
0x74 => apu.ch3.setNr33(value),
|
0x74 => apu.ch3.setNr33(value),
|
||||||
0x75 => apu.ch3.setNr34(&apu.fs, value),
|
0x75 => apu.ch3.setNr34(&apu.fs, value),
|
||||||
|
0x76, 0x77 => {},
|
||||||
|
|
||||||
0x78 => apu.ch4.setNr41(value),
|
0x78 => apu.ch4.setNr41(value),
|
||||||
0x79 => apu.ch4.setNr42(value),
|
0x79 => apu.ch4.setNr42(value),
|
||||||
|
0x7A, 0x7B => {},
|
||||||
0x7C => apu.ch4.poly.raw = value, // NR 43
|
0x7C => apu.ch4.poly.raw = value, // NR 43
|
||||||
0x7D => apu.ch4.setNr44(&apu.fs, value),
|
0x7D => apu.ch4.setNr44(&apu.fs, value),
|
||||||
|
0x7E, 0x7F => {},
|
||||||
|
|
||||||
|
0x80, 0x81 => apu.setSoundCntL(setHalf(u16, apu.psg_cnt.raw, byte_addr, value)),
|
||||||
|
0x82, 0x83 => apu.setSoundCntH(setHalf(u16, apu.dma_cnt.raw, byte_addr, value)),
|
||||||
|
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1),
|
||||||
|
0x85 => {},
|
||||||
|
0x86, 0x87 => {},
|
||||||
|
0x88, 0x89 => apu.bias.raw = setHalf(u16, apu.bias.raw, byte_addr, value), // SOUNDBIAS
|
||||||
|
0x8A...0x8F => {},
|
||||||
|
|
||||||
0x80 => apu.setNr50(value),
|
|
||||||
0x81 => apu.setNr51(value),
|
|
||||||
0x82 => apu.setSoundCntH(setLo(u16, apu.dma_cnt.raw, value)),
|
|
||||||
0x83 => apu.setSoundCntH(setHi(u16, apu.dma_cnt.raw, value)),
|
|
||||||
0x84 => apu.setSoundCntX(value >> 7 & 1 == 1), // NR52
|
|
||||||
0x89 => apu.setSoundBiasH(value),
|
|
||||||
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
0x90...0x9F => apu.ch3.wave_dev.write(T, apu.ch3.select, addr, value),
|
||||||
|
0xA0...0xA3 => log.err("Tried to write 0x{X:0>2}{} to FIFO_A", .{ value, T }),
|
||||||
|
0xA4...0xA7 => log.err("Tried to write 0x{X:0>2}{} to FIFO_B", .{ value, T }),
|
||||||
else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
|
else => util.io.write.undef(log, "Tried to write 0x{X:0>2}{} to 0x{X:0>8}", .{ value, T, addr }),
|
||||||
},
|
},
|
||||||
else => @compileError("APU: Unsupported write width"),
|
else => @compileError("APU: Unsupported write width"),
|
||||||
|
@ -256,7 +283,7 @@ pub const Apu = struct {
|
||||||
|
|
||||||
/// SOUNDCNT
|
/// SOUNDCNT
|
||||||
fn setSoundCnt(self: *Self, value: u32) void {
|
fn setSoundCnt(self: *Self, value: u32) void {
|
||||||
self.psg_cnt.raw = @truncate(u16, value);
|
self.setSoundCntL(@truncate(u16, value));
|
||||||
self.setSoundCntH(@truncate(u16, value >> 16));
|
self.setSoundCntH(@truncate(u16, value >> 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,6 +292,11 @@ pub const Apu = struct {
|
||||||
return self.psg_cnt.raw & 0xFF77;
|
return self.psg_cnt.raw & 0xFF77;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SOUNDCNT_L
|
||||||
|
pub fn setSoundCntL(self: *Self, value: u16) void {
|
||||||
|
self.psg_cnt.raw = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// SOUNDCNT_H
|
/// SOUNDCNT_H
|
||||||
pub fn setSoundCntH(self: *Self, value: u16) void {
|
pub fn setSoundCntH(self: *Self, value: u16) void {
|
||||||
const new: io.DmaSoundControl = .{ .raw = value };
|
const new: io.DmaSoundControl = .{ .raw = value };
|
||||||
|
@ -312,20 +344,6 @@ pub const Apu = struct {
|
||||||
return apu_enable << 7 | ch4_enable << 3 | ch3_enable << 2 | ch2_enable << 1 | ch1_enable;
|
return apu_enable << 7 | ch4_enable << 3 | ch3_enable << 2 | ch2_enable << 1 | ch1_enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NR50
|
|
||||||
pub fn setNr50(self: *Self, byte: u8) void {
|
|
||||||
self.psg_cnt.raw = (self.psg_cnt.raw & 0xFF00) | byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// NR51
|
|
||||||
pub fn setNr51(self: *Self, byte: u8) void {
|
|
||||||
self.psg_cnt.raw = @as(u16, byte) << 8 | (self.psg_cnt.raw & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setSoundBiasH(self: *Self, byte: u8) void {
|
|
||||||
self.bias.raw = (@as(u16, byte) << 8) | (self.bias.raw & 0xFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sampleAudio(self: *Self, late: u64) void {
|
pub fn sampleAudio(self: *Self, late: u64) void {
|
||||||
self.sched.push(.SampleAudio, self.interval() -| late);
|
self.sched.push(.SampleAudio, self.interval() -| late);
|
||||||
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ pub fn write(bus: *Bus, comptime T: type, address: u32, value: T) void {
|
||||||
0x0400_0056 => {}, // Not used
|
0x0400_0056 => {}, // Not used
|
||||||
|
|
||||||
// Sound
|
// Sound
|
||||||
0x0400_0060...0x0400_009E => apu.write(T, &bus.apu, address, value),
|
0x0400_0060...0x0400_00A6 => apu.write(T, &bus.apu, address, value),
|
||||||
|
|
||||||
// Dma Transfers
|
// Dma Transfers
|
||||||
0x0400_00B0...0x0400_00DE => dma.write(T, &bus.dma, address, value),
|
0x0400_00B0...0x0400_00DE => dma.write(T, &bus.dma, address, value),
|
||||||
|
|
Loading…
Reference in New Issue