summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices
diff options
context:
space:
mode:
author superctr <themvrules@gmail.com>2018-08-17 15:58:29 +0200
committer R. Belmont <rb6502@users.noreply.github.com>2018-08-17 09:58:29 -0400
commit3b57b7e90ca67d58ac46136aacc30b55a15e1352 (patch)
tree1087dcc17f86bf689bb70f40fd8f0b27c5b8c809 /src/devices
parent59c2a0536c857ef7ddb24559b2385dc2d8a8b154 (diff)
taito_zm: DSP emulation (work in progress!) (#3854)
* taito_zm.cpp : Updates Add DSP, Reduce MCFGs, Add device_mixer_interface for sound gain, Add imperfect_features related to DSP, Add notes * taito_zm.cpp : Fix TMS57002 clock * Improve Taito Zoom ZSG-2 sound emulation zsg2.cpp: implement emphasis filter, this is a noise reduction scheme that amplifies higher frequncies to reduce quantization noise. zsg2.cpp: Add sample interpolation and another adjustable lowpass filter. This seems to be roughly what real hardware does... zsg2.cpp: Improve panning registers and identify DSP output gain registers. * zsg2: minor changes [nw] zsg2: Register 0b appears to be status flags [nw] zsg2: Linear ramping probably makes more sense [nw] * zsg2: slight adjustment of emphasis filter [nw] * zsg2: slight adjustment of emphasis filter #2 [nw] * zsg2: more sober ramping algorithm [nw] * tms57002: add instructions 3c/3d, make them behave as NOP as they're undocumented and not understood * tms57002: Add dready callback for superctr (nw) * tms57002: Fixes to make Taito Zoom DSP working tms57002: Add undocumented instruction saom / raom, they set saturation mode for the ALU. tms57002: Implement MACC pipeline. tms57002: Add callbacks for EMPTY and PC0 pins. tms57002: Add a few unimplemented instructions. tms57002: Proper behavior of CMEM UPLOAD mode. tms57002: Fix an issue where program is not properly loaded if PLOAD is set after a program has already been written. * Documentation fix, properly identified registers as ramping control, will implement that soon [nw] * taito_zm: Working DSP emulation Pretty much OST quality now. A pretty decent upgrade from how it was previously, I'd say. * typo [nw] * just adding some quick notes about the WIP [nw] * Fix build [nw] * zsg2: Proper ramping implemenation, add register map, minor cleanups * oops [nw]
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/cpu/tms57002/tms57002.cpp108
-rw-r--r--src/devices/cpu/tms57002/tms57002.h19
-rw-r--r--src/devices/cpu/tms57002/tms57kdec.cpp9
-rw-r--r--src/devices/cpu/tms57002/tmsinstr.lst44
-rwxr-xr-xsrc/devices/cpu/tms57002/tmsmake.py18
-rw-r--r--src/devices/sound/zsg2.cpp166
-rw-r--r--src/devices/sound/zsg2.h25
7 files changed, 261 insertions, 128 deletions
diff --git a/src/devices/cpu/tms57002/tms57002.cpp b/src/devices/cpu/tms57002/tms57002.cpp
index 6e93f82040f..8a6b4ce0786 100644
--- a/src/devices/cpu/tms57002/tms57002.cpp
+++ b/src/devices/cpu/tms57002/tms57002.cpp
@@ -25,8 +25,10 @@ void tms57002_device::internal_pgm(address_map &map)
tms57002_device::tms57002_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: cpu_device(mconfig, TMS57002, tag, owner, clock)
, device_sound_interface(mconfig, *this)
- , macc(0), st0(0), st1(0), sti(0), txrd(0)
+ , macc(0), macc_read(0), macc_write(0), st0(0), st1(0), sti(0), txrd(0)
, m_dready_callback(*this)
+ , m_pc0_callback(*this)
+ , m_empty_callback(*this)
, program_config("program", ENDIANNESS_LITTLE, 32, 8, -2, address_map_constructor(FUNC(tms57002_device::internal_pgm), this))
, data_config("data", ENDIANNESS_LITTLE, 8, 20)
{
@@ -47,7 +49,9 @@ WRITE_LINE_MEMBER(tms57002_device::pload_w)
if(olds ^ sti) {
if (sti & IN_PLOAD) {
hidx = 0;
- hpc = 0;
+ pc = 0;
+ ca = 0;
+ sti &= ~(SU_MASK);
}
}
}
@@ -62,26 +66,30 @@ WRITE_LINE_MEMBER(tms57002_device::cload_w)
if(olds ^ sti) {
if (sti & IN_CLOAD) {
hidx = 0;
- ca = 0;
+ //ca = 0; // Seems extremely dubious
}
}
}
void tms57002_device::device_reset()
{
- sti = (sti & ~(SU_MASK|S_READ|S_WRITE|S_BRANCH|S_HOST)) | (SU_ST0|S_IDLE);
+ sti = (sti & ~(SU_MASK|S_READ|S_WRITE|S_BRANCH|S_HOST|S_UPDATE)) | (SU_ST0|S_IDLE);
pc = 0;
ca = 0;
hidx = 0;
id = 0;
ba0 = 0;
ba1 = 0;
+ update_counter_tail = 0;
+ update_counter_head = 0;
st0 &= ~(ST0_INCS | ST0_DIRI | ST0_FI | ST0_SIM | ST0_PLRI |
ST0_PBCI | ST0_DIRO | ST0_FO | ST0_SOM | ST0_PLRO |
ST0_PBCO | ST0_CNS);
- st1 &= ~(ST1_AOV | ST1_SFAI | ST1_SFAO | ST1_MOVM | ST1_MOV |
+ st1 &= ~(ST1_AOV | ST1_SFAI | ST1_SFAO | ST1_AOVM | ST1_MOVM | ST1_MOV |
ST1_SFMA | ST1_SFMO | ST1_RND | ST1_CRM | ST1_DBP);
update_dready();
+ update_pc0();
+ update_empty();
xba = 0;
xoa = 0;
@@ -112,6 +120,7 @@ WRITE8_MEMBER(tms57002_device::data_w)
break;
case SU_PRG:
program->write_dword(pc++, val);
+ update_pc0();
break;
}
}
@@ -121,9 +130,11 @@ WRITE8_MEMBER(tms57002_device::data_w)
host[hidx++] = data;
if(hidx >= 4) {
uint32_t val = (host[0]<<24) | (host[1]<<16) | (host[2]<<8) | host[3];
- cmem[sa] = val;
sti &= ~SU_CVAL;
- allow_update = 0;
+ update[update_counter_head] = val;
+ update_counter_head = (update_counter_head + 1) & 0x0f;
+ hidx = 1; // the write shouldn't really happen until CLOAD is high though
+ update_empty();
}
} else {
sa = data;
@@ -160,11 +171,6 @@ READ8_MEMBER(tms57002_device::data_r)
return res;
}
-READ_LINE_MEMBER(tms57002_device::empty_r)
-{
- return 1;
-}
-
READ_LINE_MEMBER(tms57002_device::dready_r)
{
return sti & S_HOST ? 0 : 1;
@@ -180,9 +186,24 @@ READ_LINE_MEMBER(tms57002_device::pc0_r)
return pc == 0 ? 0 : 1;
}
+void tms57002_device::update_pc0()
+{
+ m_pc0_callback(pc == 0 ? 0 : 1);
+}
+
+READ_LINE_MEMBER(tms57002_device::empty_r)
+{
+ return (update_counter_head == update_counter_tail);
+}
+
+void tms57002_device::update_empty()
+{
+ m_empty_callback(update_counter_head == update_counter_tail);
+}
+
WRITE_LINE_MEMBER(tms57002_device::sync_w)
{
- if(sti & (IN_PLOAD | IN_CLOAD))
+ if(sti & (IN_PLOAD /*| IN_CLOAD*/))
return;
allow_update = 1;
@@ -293,7 +314,7 @@ inline void tms57002_device::xm_step_write()
int64_t tms57002_device::macc_to_output_0(int64_t rounding, uint64_t rmask)
{
- int64_t m = macc;
+ int64_t m = macc_read;
uint64_t m1;
int over = 0;
@@ -318,7 +339,7 @@ int64_t tms57002_device::macc_to_output_0(int64_t rounding, uint64_t rmask)
int64_t tms57002_device::macc_to_output_1(int64_t rounding, uint64_t rmask)
{
- int64_t m = macc;
+ int64_t m = macc_read;
uint64_t m1;
int over = 0;
@@ -344,7 +365,7 @@ int64_t tms57002_device::macc_to_output_1(int64_t rounding, uint64_t rmask)
int64_t tms57002_device::macc_to_output_2(int64_t rounding, uint64_t rmask)
{
- int64_t m = macc;
+ int64_t m = macc_read;
uint64_t m1;
int over = 0;
@@ -370,7 +391,7 @@ int64_t tms57002_device::macc_to_output_2(int64_t rounding, uint64_t rmask)
int64_t tms57002_device::macc_to_output_3(int64_t rounding, uint64_t rmask)
{
- int64_t m = macc;
+ int64_t m = macc_read;
uint64_t m1;
int over = 0;
@@ -393,7 +414,7 @@ int64_t tms57002_device::macc_to_output_3(int64_t rounding, uint64_t rmask)
int64_t tms57002_device::macc_to_output_0s(int64_t rounding, uint64_t rmask)
{
- int64_t m = macc;
+ int64_t m = macc_read;
uint64_t m1;
int over = 0;
@@ -422,7 +443,7 @@ int64_t tms57002_device::macc_to_output_0s(int64_t rounding, uint64_t rmask)
int64_t tms57002_device::macc_to_output_1s(int64_t rounding, uint64_t rmask)
{
- int64_t m = macc;
+ int64_t m = macc_read;
uint64_t m1;
int over = 0;
@@ -452,7 +473,7 @@ int64_t tms57002_device::macc_to_output_1s(int64_t rounding, uint64_t rmask)
int64_t tms57002_device::macc_to_output_2s(int64_t rounding, uint64_t rmask)
{
- int64_t m = macc;
+ int64_t m = macc_read;
uint64_t m1;
int over = 0;
@@ -482,7 +503,7 @@ int64_t tms57002_device::macc_to_output_2s(int64_t rounding, uint64_t rmask)
int64_t tms57002_device::macc_to_output_3s(int64_t rounding, uint64_t rmask)
{
- int64_t m = macc;
+ int64_t m = macc_read;
uint64_t m1;
int over = 0;
@@ -607,6 +628,34 @@ int64_t tms57002_device::check_macc_overflow_3s()
return macc;
}
+uint32_t tms57002_device::get_cmem(uint8_t addr)
+{
+ if(sa == addr && update_counter_head != update_counter_tail)
+ sti |= S_UPDATE;
+
+ if(sti & S_UPDATE)
+ {
+ cmem[addr] = update[update_counter_tail];
+ update_counter_tail = (update_counter_tail + 1) & 0x0f;
+ update_empty();
+
+ if(update_counter_head == update_counter_tail)
+ sti &= ~S_UPDATE;
+
+ return cmem[addr]; // The value of crm is ignored during an update.
+ }
+ else
+ {
+ int crm = (st1 & ST1_CRM) >> ST1_CRM_SHIFT;
+ uint32_t cvar = cmem[addr];
+ if(crm == 1)
+ return (cvar & 0xffff0000);
+ else if(crm == 2)
+ return (cvar << 16);
+ return cvar;
+ }
+}
+
void tms57002_device::cache_flush()
{
int i;
@@ -730,7 +779,7 @@ void tms57002_device::execute_run()
{
int ipc = -1;
- while(icount > 0 && !(sti & (S_IDLE | IN_PLOAD | IN_CLOAD))) {
+ while(icount > 0 && !(sti & (S_IDLE | IN_PLOAD /*| IN_CLOAD*/))) {
int iipc;
debugger_instruction_hook(pc);
@@ -746,6 +795,9 @@ void tms57002_device::execute_run()
else
xm_step_write();
}
+
+ macc_read = macc_write;
+ macc_write = macc;
for(;;) {
uint32_t c, d;
@@ -786,8 +838,10 @@ void tms57002_device::execute_run()
} else if(sti & S_BRANCH) {
sti &= ~S_BRANCH;
ipc = -1;
- } else
+ } else {
pc++; // Wraps if it reaches 256, next wraps too
+ update_pc0();
+ }
if(rptc_next) {
rptc = rptc_next;
@@ -825,6 +879,8 @@ void tms57002_device::sound_stream_update(sound_stream &stream, stream_sample_t
void tms57002_device::device_resolve_objects()
{
m_dready_callback.resolve_safe();
+ m_pc0_callback.resolve_safe();
+ m_empty_callback.resolve_safe();
}
void tms57002_device::device_start()
@@ -861,10 +917,13 @@ void tms57002_device::device_start()
stream_alloc(4, 4, STREAM_SYNC);
save_item(NAME(macc));
+ save_item(NAME(macc_read));
+ save_item(NAME(macc_write));
save_item(NAME(cmem));
save_item(NAME(dmem0));
save_item(NAME(dmem1));
+ save_item(NAME(update));
save_item(NAME(si));
save_item(NAME(so));
@@ -893,6 +952,9 @@ void tms57002_device::device_start()
save_item(NAME(host));
save_item(NAME(hidx));
+
+ save_item(NAME(update_counter_head));
+ save_item(NAME(update_counter_tail));
save_item(NAME(allow_update));
}
diff --git a/src/devices/cpu/tms57002/tms57002.h b/src/devices/cpu/tms57002/tms57002.h
index 640aed2a55b..0db37c49fdf 100644
--- a/src/devices/cpu/tms57002/tms57002.h
+++ b/src/devices/cpu/tms57002/tms57002.h
@@ -17,6 +17,8 @@ public:
tms57002_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
auto dready_callback() { return m_dready_callback.bind(); }
+ auto pc0_callback() { return m_pc0_callback.bind(); }
+ auto empty_callback() { return m_empty_callback.bind(); }
DECLARE_READ8_MEMBER(data_r);
DECLARE_WRITE8_MEMBER(data_w);
@@ -51,7 +53,8 @@ private:
S_READ = 0x00000040,
S_WRITE = 0x00000080,
S_BRANCH = 0x00000100,
- S_HOST = 0x00000200
+ S_HOST = 0x00000200,
+ S_UPDATE = 0x00000400,
};
enum {
@@ -75,6 +78,7 @@ private:
ST1_AOV = 0x000001,
ST1_SFAI = 0x000002,
ST1_SFAO = 0x000004,
+ ST1_AOVM = 0x000008, // undocumented!
ST1_MOVM = 0x000020,
ST1_MOV = 0x000040,
ST1_SFMA = 0x000180, ST1_SFMA_SHIFT = 7,
@@ -120,7 +124,8 @@ private:
short ipc;
};
- int64_t macc;
+ // macc_read and macc_write are used by non-pipelined instructions
+ int64_t macc, macc_read, macc_write;
uint32_t cmem[256];
uint32_t dmem0[256];
@@ -136,10 +141,15 @@ private:
uint32_t xm_adr;
uint8_t host[4], hidx, allow_update;
-
+
+ uint32_t update[16];
+ uint8_t update_counter_head, update_counter_tail;
+
cd cache;
devcb_write_line m_dready_callback;
+ devcb_write_line m_pc0_callback;
+ devcb_write_line m_empty_callback;
const address_space_config program_config, data_config;
@@ -164,6 +174,8 @@ private:
inline int sfma(uint32_t st1);
void update_dready();
+ void update_pc0();
+ void update_empty();
void xm_init();
void xm_step_read();
@@ -190,6 +202,7 @@ private:
short get_hash(unsigned char adr, uint32_t st1, short *pnode);
short get_hashnode(unsigned char adr, uint32_t st1, short pnode);
int decode_get_pc();
+ uint32_t get_cmem(uint8_t addr);
};
enum {
diff --git a/src/devices/cpu/tms57002/tms57kdec.cpp b/src/devices/cpu/tms57002/tms57kdec.cpp
index 8cca7ba825f..4c6dd2dc026 100644
--- a/src/devices/cpu/tms57002/tms57kdec.cpp
+++ b/src/devices/cpu/tms57002/tms57kdec.cpp
@@ -11,6 +11,7 @@
#include "emu.h"
#include "debugger.h"
#include "tms57002.h"
+#include <algorithm>
inline int tms57002_device::xmode(uint32_t opcode, char type, cstate *cs)
{
@@ -40,6 +41,10 @@ inline int tms57002_device::dbp(uint32_t st1)
inline int tms57002_device::crm(uint32_t st1)
{
+ // value overridden during cvar update
+ if(update_counter_head != update_counter_tail)
+ return 0;
+
int crm = (st1 & ST1_CRM) >> ST1_CRM_SHIFT;
return crm <= 2 ? crm : 0;
}
@@ -90,6 +95,7 @@ void tms57002_device::decode_cat1(uint32_t opcode, unsigned short *op, cstate *c
#undef CDEC1
default:
+ logerror("Unhandled cat1 opcode %02x\n",opcode >> 18);
decode_error(opcode);
break;
}
@@ -106,6 +112,7 @@ void tms57002_device::decode_cat2_pre(uint32_t opcode, unsigned short *op, cstat
#undef CDEC2A
default:
+ logerror("Unhandled cat2_pre opcode %02x \n",(opcode >> 11) & 0x7f);
decode_error(opcode);
break;
}
@@ -122,6 +129,7 @@ void tms57002_device::decode_cat2_post(uint32_t opcode, unsigned short *op, csta
#undef CDEC2B
default:
+ logerror("Unhandled cat2_post opcode %02x\n",(opcode >> 11) & 0x7f);
decode_error(opcode);
break;
}
@@ -138,6 +146,7 @@ void tms57002_device::decode_cat3(uint32_t opcode, unsigned short *op, cstate *c
#undef CDEC3
default:
+ logerror("Unhandled cat3 opcode %02x\n",(opcode >> 11) & 0x7f);
decode_error(opcode);
break;
}
diff --git a/src/devices/cpu/tms57002/tmsinstr.lst b/src/devices/cpu/tms57002/tmsinstr.lst
index c1c0a2cf658..9160d9ecd5b 100644
--- a/src/devices/cpu/tms57002/tmsinstr.lst
+++ b/src/devices/cpu/tms57002/tmsinstr.lst
@@ -219,17 +219,17 @@ lirk 3 20 1 n
lmhc 1 33 1 n
lmhc %c
- macc = ((int64_t)(int32_t)%c) << 16;
+ macc_write = macc = ((int64_t)(int32_t)%c) << 16;
lmhd 1 31 1 n
lmhd %d
- macc = ((int64_t)(int32_t)%d) << 16;
+ macc_write = macc = ((int64_t)(int32_t)%d) << 16;
lmld 1 32 1 n
lmld %d
- macc = (macc & ~0xffffffULL) | %d24;
+ macc_write = macc = (macc & ~0xffffffULL) | %d24;
-lpc 2b 31 1 n
+lpc 2a 31 1 n
lpc %c
if(sti & S_HOST)
break;
@@ -242,7 +242,7 @@ lpc 2b 31 1 n
sti |= S_HOST;
update_dready();
-lpd 2b 30 1 n
+lpd 2a 30 1 n
lpd %d
mac 1 24 1 y
@@ -256,6 +256,12 @@ mac 1 24 1 y
mac 1 25 1 y
mac a,%d
+ d = %d24;
+ if(d & 0x00800000)
+ d |= 0xff000000;
+ creg = c = %a;
+ r = (int64_t)(int32_t)c * (int64_t)(int32_t)d;
+ macc = %ml + (r >> 7);
mac 1 26 1 y
mac %c,a
@@ -310,6 +316,7 @@ mpyu 1 28 1 y
neg 1 02 1 n
neg
+ %wa(-(int64_t)%a);
or 1 17 1 n
or %d,a
@@ -408,11 +415,11 @@ scrm 2a 4b 1 n f
scrm <3>
st1 = (st1 & ~ST1_CRM) | (3 << ST1_CRM_SHIFT);
-sfai 2b 54 1 n f
+sfai 2a 54 1 n f
sfai 0
st1 &= ~ST1_SFAI;
-sfai 2b 55 1 n f
+sfai 2a 55 1 n f
sfai -1
st1 |= ST1_SFAI;
@@ -422,21 +429,21 @@ sfao 2a 50 1 n f
sfao 2a 51 1 n f
sfao 7
- st1 |= ST1_SFAI;
+ st1 |= ST1_SFAO;
-sfma 2b 58 1 n f
+sfma 2a 58 1 n f
sfma 0
st1 = (st1 & ~ST1_SFMA) | (0 << ST1_SFMA_SHIFT);
-sfma 2b 59 1 n f
+sfma 2a 59 1 n f
sfma 2
st1 = (st1 & ~ST1_SFMA) | (1 << ST1_SFMA_SHIFT);
-sfma 2b 5a 1 n f
+sfma 2a 5a 1 n f
sfma 4
st1 = (st1 & ~ST1_SFMA) | (2 << ST1_SFMA_SHIFT);
-sfma 2b 5b 1 n f
+sfma 2a 5b 1 n f
sfma -16
st1 = (st1 & ~ST1_SFMA) | (3 << ST1_SFMA_SHIFT);
@@ -504,9 +511,12 @@ sub 1 0a 1 y
sub 1 0b 1 y
sub %d,m
+ %sfai(d, %d);
+ %wa((int64_t)(int32_t)d - (%mo >> 16));
sub 1 0c 1 y
sub %c,m
+ %wa((int64_t)(int32_t)%c - (%mo >> 16));
sub 1 0d 1 y
sub %d,%c
@@ -535,3 +545,13 @@ zacc 1 10 1 n
zmac 1 30 1 n
zmac
+
+raom 2a 3c 1 n
+ raom
+ /* Undocumented instruction, reset ALU saturation flag */
+ st1 &= ~ST1_AOVM;
+
+saom 2a 3d 1 n
+ saom
+ /* Undocumented instruction, sets ALU saturation flag */
+ st1 |= ST1_AOVM;
diff --git a/src/devices/cpu/tms57002/tmsmake.py b/src/devices/cpu/tms57002/tmsmake.py
index 19336061c11..3b5c4a0f6e2 100755
--- a/src/devices/cpu/tms57002/tmsmake.py
+++ b/src/devices/cpu/tms57002/tmsmake.py
@@ -23,11 +23,6 @@ TYPES = {
"f": None,
}
-def expand_c(v):
- fmt = ["%s", "(%s & 0xffff0000)", "(%s << 16)"][v["crm"]]
- param = ["cmem[i->param]", "cmem[ca]"][v["cmode"]]
- return fmt % param
-
def expand_d(v):
index = ["(i->param + ", "(id + "][v["dmode"]]
mask = ["ba0) & 0xff] << 8)", "ba1) & 0x1f] << 8)"][v["dbp"]]
@@ -44,6 +39,8 @@ def expand_mv(v):
c = ["", "s"][v["movm"]]
return "check_macc_overflow_%d%s()" % (v["sfmo"], c)
+
+EXPAND_C = ["get_cmem(i->param)", "get_cmem(ca)"]
EXPAND_WC = ["cmem[i->param] =", "cmem[ca] ="]
@@ -70,14 +67,15 @@ def expand_wd1(v):
return "dmem%d[" % v["dbp"] + index + mask
WA2 = (
-" if(r < -0x80000000 || r > 0x7fffffff)\n"
+" if(r < -2147483648 || r > 2147483647) {\n"
" st1 |= ST1_AOV;\n"
+" if(st1 & ST1_AOVM) r = std::max(int64_t(-2147483648), std::min(int64_t(2147483647), r));\n"
+" }"
" aacc = r;")
-
PDESC_EXPAND = {
"a": lambda v: ["aacc", "(aacc << 7)"][v["sfao"]],
- "c": expand_c,
+ "c": lambda v: EXPAND_C[v["cmode"]],
"d": expand_d,
"d24": expand_d24,
"i": lambda v: "i->param",
@@ -97,7 +95,7 @@ PDESC_EXPAND = {
PDESC = {
"a": (0, ["sfao"]),
- "c": (0, ["cmode", "crm"]),
+ "c": (0, ["cmode"]),
"d": (0, ["dmode", "dbp"]),
"d24": (0, ["dmode", "dbp"]),
"i": (0, []),
@@ -115,7 +113,6 @@ VARIANTS = {
"cmode": (2, "xmode(opcode, 'c', cs)" ),
"dmode": (2, "xmode(opcode, 'd', cs)" ),
"sfai": (2, "sfai(st1)"),
- "crm": (3, "crm(st1)"),
"dbp": (2, "dbp(st1)"),
"sfao": (2, "sfao(st1)"),
"sfmo": (4, "sfmo(st1)"),
@@ -131,7 +128,6 @@ VARIANT_CANONICAL_ORDER = [
"cmode",
"dmode",
"sfai",
- "crm",
"dbp",
"sfao",
"sfmo",
diff --git a/src/devices/sound/zsg2.cpp b/src/devices/sound/zsg2.cpp
index 9e0eb89bdb8..de2b9dad263 100644
--- a/src/devices/sound/zsg2.cpp
+++ b/src/devices/sound/zsg2.cpp
@@ -6,9 +6,48 @@
Written by Olivier Galibert
MAME conversion by R. Belmont
Working emulation by The Talentuous Hands Of The Popularious hap
- Improvements by superctr
+ Properly working emulation by superctr
---------------------------------------------------------
+ Register map:
+ 000-5fe : Channel specific registers
+ (high) (low)
+ +000 : xxxxxxxx -------- : Start address (low)
+ +000 : -------- xxxxxxxx : Unknown register (usually cleared)
+ +002 : xxxxxxxx -------- : Address page
+ : -------- xxxxxxxx : Start address (high)
+ +004 : -------- -------- : Unknown register (usually cleared)
+ +006 : -----x-- -------- : Unknown bit, always set
+ +008 : xxxxxxxx xxxxxxxx : Frequency
+ +00a : xxxxxxxx -------- : DSP ch 3 (right) output gain
+ : -------- xxxxxxxx : Loop address (low)
+ +00c : xxxxxxxx xxxxxxxx : End address
+ +00e : xxxxxxxx -------- : DSP ch 2 (Left) output gain
+ : -------- xxxxxxxx : Loop address (high)
+ +010 : xxxxxxxx xxxxxxxx : Filter time constant (latch)
+ +012 : -------- -------- : Unknown register (usually cleared)
+ +014 : xxxxxxxx xxxxxxxx : Volume (latch)
+ +016 : --x----- -------- : Key on status flag (only read)
+ +018 : xxxxxxxx xxxxxxxx : Filter time constant (Ramping target)
+ +01a : xxxxxxxx -------- : DSP ch 1 (chorus) output gain
+ : -------- xxxxxxxx : Filter ramping speed
+ +01c : xxxxxxxx xxxxxxxx : Volume (target)
+ +01e : xxxxxxxx -------- : DSP ch 0 (reverb) output gain
+ : -------- xxxxxxxx : Filter ramping speed
+ 600-604 : Key on flags (each bit corresponds to a channel)
+ 608-60c : Key off flags (each bit corresponds to a channel)
+ 618 : Unknown register (usually 0x5cbc is written)
+ 61a : Unknown register (usually 0x5cbc is written)
+ 620 : Unknown register (usually 0x0128 is written)
+ 628 : Unknown register (usually 0x0066 is written)
+ 630 : Unknown register (usually 0x0001 is written)
+ 638 : ROM readback address low
+ 63a : ROM readback address high
+ 63c : ROM readback word low
+ 63e : ROM readback word high
+
+ ---------------------------------------------------------
+
Additional notes on the sample format, reverse-engineered
by Olivier Galibert and David Haywood:
@@ -45,12 +84,8 @@
---------------------------------------------------------
TODO:
-- Filter behavior might not be perfect.
-- Volume ramping probably behaves differently on hardware.
-- hook up DSP, it's used for reverb and chorus effects.
-- identify sample flags
- * bassdrum in shikigam level 1 music is a good hint: it should be one octave
- lower, indicating possible stereo sample, or base octave(like in ymf278)
+- Filter and ramping behavior might not be perfect.
+- sometimes clicking
- memory reads out of range sometimes
*/
@@ -63,7 +98,6 @@ TODO:
#include <cmath>
#define EMPHASIS_CUTOFF_BASE 0x800
-#define EMPHASIS_CUTOFF_SHIFT 1
#define EMPHASIS_OUTPUT_SHIFT 15
// device type definition
@@ -104,16 +138,18 @@ void zsg2_device::device_start()
save_item(NAME(m_read_address));
// Generate the output gain table. Assuming -1dB per step for now.
- for (int i = 0; i < 32; i++)
+ for (int i = 0, history=0; i < 32; i++)
{
double val = pow(10, -(31 - i) / 20.) * 65535.;
gain_tab[i] = val;
+ gain_tab_frac[i] = val-history;
+ history = val;
}
for (int ch = 0; ch < 48; ch++)
{
save_item(NAME(m_chan[ch].v), ch);
- save_item(NAME(m_chan[ch].is_playing), ch);
+ save_item(NAME(m_chan[ch].status), ch);
save_item(NAME(m_chan[ch].cur_pos), ch);
save_item(NAME(m_chan[ch].step_ptr), ch);
save_item(NAME(m_chan[ch].step), ch);
@@ -125,14 +161,12 @@ void zsg2_device::device_start()
save_item(NAME(m_chan[ch].vol), ch);
save_item(NAME(m_chan[ch].vol_initial), ch);
save_item(NAME(m_chan[ch].vol_target), ch);
-
- save_item(NAME(m_chan[ch].emphasis_cutoff), ch);
- save_item(NAME(m_chan[ch].emphasis_cutoff_initial), ch);
- save_item(NAME(m_chan[ch].emphasis_cutoff_target), ch);
+ save_item(NAME(m_chan[ch].vol_delta), ch);
save_item(NAME(m_chan[ch].output_cutoff), ch);
save_item(NAME(m_chan[ch].output_cutoff_initial), ch);
save_item(NAME(m_chan[ch].output_cutoff_target), ch);
+ save_item(NAME(m_chan[ch].output_cutoff_delta), ch);
save_item(NAME(m_chan[ch].emphasis_filter_state), ch);
save_item(NAME(m_chan[ch].output_filter_state), ch);
@@ -141,6 +175,8 @@ void zsg2_device::device_start()
save_item(NAME(m_chan[ch].samples), ch);
}
+
+ save_item(NAME(m_sample_count));
}
//-------------------------------------------------
@@ -159,6 +195,7 @@ void zsg2_device::device_reset()
for (int ch = 0; ch < 48; ch++)
for (int reg = 0; reg < 0x10; reg++)
chan_w(ch, reg, 0);
+ m_sample_count = 0;
#if 0
for (int i = 0; i < m_mem_blocks; i++)
@@ -232,7 +269,7 @@ void zsg2_device::filter_samples(zchan *ch)
// not sure if the filter works exactly this way, however I am pleased
// with the output for now.
- ch->emphasis_filter_state += (raw_samples[i]-(ch->emphasis_filter_state>>16)) * (EMPHASIS_CUTOFF_BASE - ch->emphasis_cutoff);
+ ch->emphasis_filter_state += (raw_samples[i]-(ch->emphasis_filter_state>>16)) * EMPHASIS_CUTOFF_BASE;
ch->samples[i+1] = (ch->emphasis_filter_state) >> EMPHASIS_OUTPUT_SHIFT;
}
}
@@ -243,6 +280,7 @@ void zsg2_device::filter_samples(zchan *ch)
void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
+ // DSP is programmed to expect 24-bit samples! So we're not limiting to 16-bit here
for (int i = 0; i < samples; i++)
{
int32_t mix[4] = {};
@@ -254,7 +292,7 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in
//auto & elem = m_chan[0];
{
ch++;
- if (!elem.is_playing)
+ if (!(elem.status & STATUS_ACTIVE))
continue;
elem.step_ptr += elem.step;
@@ -268,12 +306,11 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in
if ((elem.cur_pos + 1) >= elem.end_pos)
{
// end of sample
- elem.is_playing = false;
+ elem.status &= ~STATUS_ACTIVE;
continue;
}
}
filter_samples(&elem);
- //elem.samples = prepare_samples(elem.page | elem.cur_pos);
}
uint8_t sample_pos = elem.step_ptr >> 14 & 3;
@@ -282,6 +319,7 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in
// linear interpolation (hardware certainly does something similar)
sample = elem.samples[sample_pos];
sample += ((uint16_t)(elem.step_ptr<<2&0xffff) * (int16_t)(elem.samples[sample_pos+1] - sample))>>16;
+
sample = (sample * elem.vol) >> 16;
// another filter...
@@ -299,10 +337,13 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in
mix[output] += (output_sample * gain_tab[output_gain&0x1f]) >> 13;
}
- // Apply transitions (This is not accurate yet)
- elem.vol = ramp(elem.vol, elem.vol_target);
- elem.output_cutoff = ramp(elem.output_cutoff, elem.output_cutoff_target);
- elem.emphasis_cutoff = ramp(elem.emphasis_cutoff, elem.emphasis_cutoff_target);
+ // Apply ramping every other update
+ // It's possible key on is handled on the other sample
+ if(m_sample_count & 1)
+ {
+ elem.vol = ramp(elem.vol, elem.vol_target, elem.vol_delta);
+ elem.output_cutoff = ramp(elem.output_cutoff, elem.output_cutoff_target, elem.output_cutoff_delta);
+ }
}
ch = 0;
@@ -311,6 +352,7 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in
outputs[output][i] = mix[output];
}
+ m_sample_count++;
}
/******************************************************************************/
@@ -347,9 +389,9 @@ void zsg2_device::chan_w(int ch, int reg, uint16_t data)
case 0x5:
// lo byte: loop address low
- // hi byte: right output gain (bypass DSP)
+ // hi byte: right output gain (direct)
m_chan[ch].loop_pos = (m_chan[ch].loop_pos & 0xff00) | (data & 0xff);
- m_chan[ch].output_gain[1] = data >> 8;
+ m_chan[ch].output_gain[3] = data >> 8;
break;
case 0x6:
@@ -359,13 +401,13 @@ void zsg2_device::chan_w(int ch, int reg, uint16_t data)
case 0x7:
// lo byte: loop address high
- // hi byte: left output gain (bypass DSP)
+ // hi byte: left output gain (direct)
m_chan[ch].loop_pos = (m_chan[ch].loop_pos & 0x00ff) | (data << 8 & 0xff00);
- m_chan[ch].output_gain[0] = data >> 8;
+ m_chan[ch].output_gain[2] = data >> 8;
break;
case 0x8:
- // Filter cutoff (Direct)
+ // IIR lowpass time constant (initial, latched on key on)
m_chan[ch].output_cutoff_initial = data;
break;
@@ -374,7 +416,7 @@ void zsg2_device::chan_w(int ch, int reg, uint16_t data)
break;
case 0xa:
- // volume (Direct)
+ // volume (initial, latched on key on)
m_chan[ch].vol_initial = data;
break;
@@ -384,27 +426,27 @@ void zsg2_device::chan_w(int ch, int reg, uint16_t data)
break;
case 0xc:
- // filter gain ?
+ // IIR lowpass time constant (target)
m_chan[ch].output_cutoff_target = data;
break;
case 0xd:
- // hi byte: DSP Chorus volume
- // lo byte: Emphasis filter time constant (direct value)
- m_chan[ch].output_gain[3] = data >> 8;
- m_chan[ch].emphasis_cutoff_initial = expand_reg(data & 0xff);
+ // hi byte: DSP channel 1 (chorus) gain
+ // lo byte: Filter ramping speed
+ m_chan[ch].output_gain[1] = data >> 8;
+ m_chan[ch].output_cutoff_delta = get_ramp(data & 0xff);
break;
case 0xe:
- // volume (Target)
+ // volume target
m_chan[ch].vol_target = data;
break;
case 0xf:
- // hi byte: DSP Reverb volume
- // lo byte: Emphasis filter time constant
- m_chan[ch].output_gain[2] = data >> 8;
- m_chan[ch].emphasis_cutoff_target = expand_reg(data & 0xff);
+ // hi byte: DSP channel 0 (reverb) gain
+ // lo byte: Volume ramping speed
+ m_chan[ch].output_gain[0] = data >> 8;
+ m_chan[ch].vol_delta = get_ramp(data & 0xff);
break;
default:
@@ -419,7 +461,7 @@ uint16_t zsg2_device::chan_r(int ch, int reg)
switch (reg)
{
case 0xb: // Only later games (taitogn) read this register...
- return m_chan[ch].is_playing << 13;
+ return m_chan[ch].status;
default:
break;
}
@@ -427,37 +469,30 @@ uint16_t zsg2_device::chan_r(int ch, int reg)
return m_chan[ch].v[reg];
}
-// expand 8-bit reg to 16-bit value. This is used for the emphasis filter
-// register. Not sure about how this works, the sound
+// Convert ramping register value to something more usable.
+// Upper 4 bits is a shift amount, lower 4 bits is a 2's complement value.
+// Get ramp amount by sign extending the low 4 bits, XOR by 8, then
+// shifting it by the upper 4 bits.
// CPU uses a lookup table (stored in gdarius sound cpu ROM at 0x6332) to
// calculate this value, for now I'm generating an opproximate inverse.
-int16_t zsg2_device::expand_reg(uint8_t val)
+int16_t zsg2_device::get_ramp(uint8_t val)
{
- static const signed char frac_tab[16] = {8,9,10,11,12,13,14,15,-15,-14,-13,-12,-11,-10,-9,-8};
- static const unsigned char shift_tab[8] = {1, 2, 3, 4, 5, 6, 7, 8};
-
- return (frac_tab[val&0x0f] << shift_tab[val>>4])>>EMPHASIS_CUTOFF_SHIFT;
+ int16_t frac = val<<12; // sign extend
+ frac = ((frac>>12) ^ 8) << (val >> 4);
+
+ return (frac >> 4);
}
-// ramp registers
-// The CPU does not write often enough to make the transitions always sound
-// smooth, so the sound chip probably helps by smoothing the changes.
-// There are two sets of the volume and filter cutoff registers.
-// At key on, the CPU writes to the "direct" registers, after that it will
-// write to the "target" register instead.
-inline int32_t zsg2_device::ramp(int32_t current, int32_t target)
+inline uint16_t zsg2_device::ramp(uint16_t current, uint16_t target, int16_t delta)
{
- int32_t difference = abs(target-current);
- difference -= 0x40;
+ int32_t rampval = current + delta;
- if(difference < 0)
- return target;
- else if(target < current)
- return target + difference;
- else if(target > current)
- return target - difference;
-
- return target;
+ if(delta < 0 && rampval < target)
+ rampval = target;
+ else if(delta >= 0 && rampval > target)
+ rampval = target;
+
+ return rampval;
}
/******************************************************************************/
@@ -475,13 +510,12 @@ void zsg2_device::control_w(int reg, uint16_t data)
if (data & (1 << i))
{
int ch = base | i;
- m_chan[ch].is_playing = true;
+ m_chan[ch].status |= STATUS_ACTIVE;
m_chan[ch].cur_pos = m_chan[ch].start_pos;
m_chan[ch].step_ptr = 0;
m_chan[ch].emphasis_filter_state = 0;
m_chan[ch].vol = m_chan[ch].vol_initial;
m_chan[ch].output_cutoff = m_chan[ch].output_cutoff_initial;
- m_chan[ch].emphasis_cutoff = m_chan[ch].emphasis_cutoff_initial;
filter_samples(&m_chan[ch]);
}
}
@@ -497,7 +531,7 @@ void zsg2_device::control_w(int reg, uint16_t data)
if (data & (1 << i))
{
int ch = base | i;
- m_chan[ch].is_playing = false;
+ m_chan[ch].status &= ~STATUS_ACTIVE;
}
}
break;
diff --git a/src/devices/sound/zsg2.h b/src/devices/sound/zsg2.h
index 10ebb799b6f..18ec9304775 100644
--- a/src/devices/sound/zsg2.h
+++ b/src/devices/sound/zsg2.h
@@ -1,5 +1,5 @@
// license:BSD-3-Clause
-// copyright-holders:Olivier Galibert, R. Belmont, hap
+// copyright-holders:Olivier Galibert, R. Belmont, hap, superctr
/*
ZOOM ZSG-2 custom wavetable synthesizer
*/
@@ -14,11 +14,6 @@
// INTERFACE CONFIGURATION MACROS
//**************************************************************************
-#define MCFG_ZSG2_ADD(_tag, _clock) \
- MCFG_DEVICE_ADD(_tag, ZSG2, _clock)
-#define MCFG_ZSG2_REPLACE(_tag, _clock) \
- MCFG_DEVICE_REPLACE(_tag, ZSG2, _clock)
-
#define MCFG_ZSG2_EXT_READ_HANDLER(_devcb) \
downcast<zsg2_device &>(*device).set_ext_read_handler(DEVCB_##_devcb);
@@ -46,11 +41,14 @@ protected:
virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;
private:
+ const uint16_t STATUS_ACTIVE = 0x2000;
+
// 16 registers per channel, 48 channels
struct zchan
{
uint16_t v[16];
- bool is_playing;
+
+ uint16_t status;
uint32_t cur_pos;
uint32_t step_ptr;
uint32_t step;
@@ -62,14 +60,12 @@ private:
uint16_t vol;
uint16_t vol_initial;
uint16_t vol_target;
-
- int16_t emphasis_cutoff;
- int16_t emphasis_cutoff_initial;
- int16_t emphasis_cutoff_target;
+ int16_t vol_delta;
uint16_t output_cutoff;
uint16_t output_cutoff_initial;
uint16_t output_cutoff_target;
+ int16_t output_cutoff_delta;
int32_t emphasis_filter_state;
int32_t output_filter_state;
@@ -81,7 +77,10 @@ private:
};
uint16_t gain_tab[256];
+ uint16_t gain_tab_frac[256];
+
zchan m_chan[48];
+ uint32_t m_sample_count;
required_region_ptr<uint32_t> m_mem_base;
uint32_t m_read_address;
@@ -100,8 +99,8 @@ private:
uint16_t control_r(int reg);
int16_t *prepare_samples(uint32_t offset);
void filter_samples(zchan *ch);
- int16_t expand_reg(uint8_t val);
- inline int32_t ramp(int32_t current, int32_t target);
+ int16_t get_ramp(uint8_t val);
+ inline uint16_t ramp(uint16_t current, uint16_t target, int16_t delta);
};
DECLARE_DEVICE_TYPE(ZSG2, zsg2_device)