Skip to content

Commit

Permalink
DMD: generic PWM support, Data East 128x32 support, GTS3 cleanups, Al…
Browse files Browse the repository at this point in the history
…vin.G preliminary cleanups
  • Loading branch information
vbousquet committed Aug 31, 2024
1 parent f863cb6 commit 8f8fc5c
Show file tree
Hide file tree
Showing 15 changed files with 280 additions and 216 deletions.
10 changes: 10 additions & 0 deletions src/vidhrdw/crtc6845.c
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,17 @@ int crtc6845_start_address_r(int offset)
{
return crtc6845[offset].start_addr;
}
//Return rasterization size
int crtc6845_get_rasterized_height(int offset)
{
// height in scanlines is the number of displayed character line * number of scanlines per character
return crtc6845[offset].vert_disp * (crtc6845[offset].max_ras_addr + 1);
}

int crtc6845_get_rasterized_width(int offset)
{
return crtc6845[offset].horiz_disp;
}



Expand Down
4 changes: 4 additions & 0 deletions src/vidhrdw/crtc6845.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ WRITE_HANDLER( crtc6845_register_w );
//Return current video start address
int crtc6845_start_address_r(int offset);

//Return rasterization size
int crtc6845_get_rasterized_height(int offset);
int crtc6845_get_rasterized_width(int offset);

/*Convenience handlers*/
READ_HANDLER( crtc6845_register_0_r );
WRITE_HANDLER( crtc6845_address_0_w );
Expand Down
81 changes: 42 additions & 39 deletions src/wpc/alvgdmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ extern UINT32 g_raw_gtswpc_dmdframes;

static struct {
struct sndbrdData brdData;
int cmd, planenable, disenable, setsync;
int cmd, planenable, disenable;
int selsync; // SELSYNC signal on PCA020A board (Mystery Castle, Pistol Poker). Enable/Disable rasterization VCLOCK. Not present on PCA020 board (Al's Garage Band).
int vid_page;

int last;
Expand All @@ -52,63 +53,64 @@ const struct sndbrdIntf alvgdmdIntf = {
dmd32_data_w, NULL, dmd32_ctrl_w, NULL, SNDBRD_NOTSOUND
};


/*Main CPU sends command to DMD*/
static WRITE_HANDLER(dmd32_data_w) {
dmdlocals.cmd = data;
}

static WRITE_HANDLER(control_w)
{
UINT16 tmpoffset = offset;
tmpoffset += 0x8000; //Adjust the offset so we can work with the "actual address" as it really is (just makes it easier, it's not necessary to do this)
tmpoffset &= 0xf000; //Remove bits 0-11 (in other words, treat 0x8fff the same as 0x8000!)
switch (tmpoffset)
{
//Read Main CPU DMD Command
//Read Main CPU DMD Command [PORTIN signal]
case 0x8000:
LOG(("WARNING! Writing to address 0x8000 - DMD Latch, data=%x!\n",data));
dmdlocals.selsync = 1;
break;

//Send Data to the Main CPU
//Send Data to the Main CPU [PORTOUT signal]
case 0x9000:
sndbrd_ctrl_cb(dmdlocals.brdData.boardNo, data);
dmdlocals.selsync = 1;
break;

//ROM Bankswitching
//ROM Bankswitching [CODEPAGE signal]
case 0xa000:
dmd32_bank_w(0,data);
dmdlocals.selsync = 1;
break;

//ROWSTART Line
//ROWSTART Line [ROWSTART signal]
case 0xb000:
LOG(("rowstart = %x\n",data));
dmdlocals.selsync = 1;
break;

//COLSTART Line
//COLSTART Line [COLSTART signal]
case 0xc000:
LOG(("colstart = %x\n",data));
dmdlocals.selsync = 1;
break;

//NC
case 0xd000:
case 0xe000:
LOG(("writing to not connected address: %x data=%x\n",offset,data));
dmdlocals.selsync = 1;
break;

//SETSYNC Line
//SELSYNC Line [SELSYNC signal on PCA020A, not wired on PCA020]
//SelSync suspend rasterization (stops rasterization clock) if written to, only the address bus is used, so writing to any other address in 0x8000 - 0xE000 will reset it
//it doesn't seem to be used (didn't find a write to it on Pistol Poker & Mystery Castle)
// FIXME remove for hardware without it
case 0xf000:
dmdlocals.setsync=data;
LOG(("setsync=%x\n",data));
LOG(("setsync=%x\n", data));
dmdlocals.selsync = 0;
printf("%8.5f selsync off\n", timer_get_time());
break;

default:
LOG(("WARNING! Reading invalid control address %x\n", offset));
break;
}
//Setsync line goes hi for all address except it's own(0xf000)
if(offset != 0xf000) {
dmdlocals.setsync=1;
}
}

static READ_HANDLER(control_r)
Expand All @@ -121,18 +123,25 @@ static READ_HANDLER(control_r)
//Read Main CPU DMD Command
case 0x8000: {
int data = dmd32_latch_r(0);
dmdlocals.selsync = 1;
return data;
}
//While unlikely, a READ to these addresses, can actually trigger control lines, so we call control_w()

case 0x9000:
case 0xa000:
case 0xb000:
case 0xc000:
case 0xd000:
case 0xe000:
// undefined behavior as data is not set
dmdlocals.selsync = 1;
break;

// see control_w (data is unused so it has the same effect)
case 0xf000:
control_w(offset,0);
dmdlocals.selsync = 0;
break;

default:
LOG(("WARNING! Reading invalid control address %x\n", offset));
}
Expand Down Expand Up @@ -259,7 +268,12 @@ static void dmd32_init(struct sndbrdData *brdData) {
memset(&dmdlocals, 0, sizeof(dmdlocals));
dmdlocals.brdData = *brdData;
dmd32_bank_w(0,0); //Set DMD Bank to 0
dmdlocals.setsync = 1; //Start Sync @ 1
dmdlocals.selsync = 1; //Start Sync @ 1
}

// Main CPU sends command to DMD
static WRITE_HANDLER(dmd32_data_w) {
dmdlocals.cmd = data;
}

//Send data from Main CPU to latch - Set's the INT0 Line
Expand All @@ -277,7 +291,7 @@ static READ_HANDLER(dmd32_latch_r) {

//Pulse the INT1 Line
static INTERRUPT_GEN(dmd32_firq) {
if(dmdlocals.setsync) {
if(dmdlocals.selsync) {
LOG(("INT1 Pulse\n"));
cpu_set_irq_line(dmdlocals.brdData.cpuNo, I8051_INT1_LINE, PULSE_LINE);
}
Expand All @@ -286,17 +300,6 @@ static INTERRUPT_GEN(dmd32_firq) {
}
}

#if defined(VPINMAME) || defined(LIBPINMAME)
static const unsigned char lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };

INLINE UINT8 reverse(UINT8 n) {
// Reverse the top and bottom nibble then swap them.
return (lookup[n & 0x0f] << 4) | lookup[n >> 4];
}
#endif

PINMAME_VIDEO_UPDATE(alvgdmd_update) {
#ifdef MAME_DEBUG
static int offset = 0;
Expand Down Expand Up @@ -377,10 +380,10 @@ static void pistol_poker__mystery_castle_dmd(void) {
*line++ = level[((RAM[0] >> 1 & 0x01) + (RAM[0x200] >> 1 & 0x01) + (RAM[0x400] >> 1 & 0x01) + (RAM[0x600] >> 1 & 0x01))];
*line++ = level[((RAM[0]/*>>0*/&0x01) + (RAM[0x200]/*>>0*/&0x01) + (RAM[0x400]/*>>0*/&0x01) + (RAM[0x600]/*>>0*/&0x01))];
#if defined(VPINMAME) || defined(LIBPINMAME)
g_raw_gtswpc_dmd[ i] = reverse(RAM[0]);
g_raw_gtswpc_dmd[0x200 + i] = reverse(RAM[0x200]);
g_raw_gtswpc_dmd[0x400 + i] = reverse(RAM[0x400]);
g_raw_gtswpc_dmd[0x600 + i] = reverse(RAM[0x600]);
g_raw_gtswpc_dmd[ i] = core_revbyte(RAM[0]);
g_raw_gtswpc_dmd[0x200 + i] = core_revbyte(RAM[0x200]);
g_raw_gtswpc_dmd[0x400 + i] = core_revbyte(RAM[0x400]);
g_raw_gtswpc_dmd[0x600 + i] = core_revbyte(RAM[0x600]);
i++;
#endif
RAM += 1;
Expand All @@ -403,7 +406,7 @@ static void pistol_poker__mystery_castle_dmd(void) {
g_raw_gtswpc_dmd[ i] =
g_raw_gtswpc_dmd[0x200 + i] =
g_raw_gtswpc_dmd[0x400 + i] =
g_raw_gtswpc_dmd[0x600 + i] = reverse(RAM[0]);
g_raw_gtswpc_dmd[0x600 + i] = core_revbyte(RAM[0]);
i++;
#endif
RAM += 1;
Expand Down
114 changes: 108 additions & 6 deletions src/wpc/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
static UINT8 has_DMD_Video = 0;

#include "gts3dmd.h"
UINT8 g_raw_gtswpc_dmd[GTS3DMD_FRAMES*0x200];
UINT8 g_raw_gtswpc_dmd[16*0x200]; // TODO cleanup raw frame sharing
UINT32 g_raw_gtswpc_dmdframes = 0;

UINT8 g_needs_DMD_update = 1;
Expand Down Expand Up @@ -940,15 +940,16 @@ void video_update_core_dmd(struct mame_bitmap *bitmap, const struct rectangle *c
}
}
#endif

// The PWM DMD implementation is always returning a full byte, so we shift it down here to return the expected value to existing code
const int shift = ((core_gameData->gen & (GEN_GTS3)) != 0) ? 4 /* 256 to 16 shades */ : ((core_gameData->gen & (GEN_ALLWPC | GEN_DEDMD32)) != 0) ? 6 /* 256 to 4 shades */ : 0;
memset(&coreGlobals.dotCol[layout->start+1][0], 0, sizeof(coreGlobals.dotCol[0][0])*layout->length+1);
memset(&coreGlobals.dotCol[0][0], 0, sizeof(coreGlobals.dotCol[0][0])*layout->length+1); // clear above
for (ii = 0; ii < layout->start+1; ii++) {
BMTYPE *line = (*lines++) + (layout->left*locals.displaySize);
coreGlobals.dotCol[ii][layout->length] = 0;
if (ii > 0) {
for (jj = 0; jj < layout->length; jj++) {
const UINT8 col = coreGlobals.dotCol[ii][jj];
const UINT8 col = coreGlobals.dotCol[ii][jj] >> shift;
#if defined(VPINMAME) || defined(LIBPINMAME)
const int offs = (ii-1)*layout->length + jj;
currbuffer[offs] = col;
Expand All @@ -963,14 +964,14 @@ void video_update_core_dmd(struct mame_bitmap *bitmap, const struct rectangle *c
#endif
*line++ = shade_16_enabled ? dmdColor[col+63] : dmdColor[col];
if (locals.displaySize > 1 && jj < layout->length-1)
*line++ = noaa ? 0 : aaColor[col + coreGlobals.dotCol[ii][jj+1]];
*line++ = noaa ? 0 : aaColor[col + (coreGlobals.dotCol[ii][jj+1] >> shift)];
}
}
if (locals.displaySize > 1) {
int col1 = coreGlobals.dotCol[ii][0] + coreGlobals.dotCol[ii+1][0];
int col1 = (coreGlobals.dotCol[ii][0] >> shift) + (coreGlobals.dotCol[ii+1][0] >> shift);
line = (*lines++) + (layout->left*locals.displaySize);
for (jj = 0; jj < layout->length; jj++) {
int col2 = coreGlobals.dotCol[ii][jj+1] + coreGlobals.dotCol[ii+1][jj+1];
int col2 = (coreGlobals.dotCol[ii][jj+1] >> shift) + (coreGlobals.dotCol[ii+1][jj+1] >> shift);
*line++ = noaa ? 0 : aaColor[col1];
if (jj < layout->length-1)
*line++ = noaa ? 0 : aaColor[2*(col1 + col2)/5];
Expand Down Expand Up @@ -2942,6 +2943,107 @@ void core_write_pwm_output_lamp_matrix(int startIndex, UINT8 columns, UINT8 rows
}
}


//
//
//

/* The following script allows to compute and display the filter applied to some PWM pattern in GNU Octave:
pkg load signal
fc = 15; % Cut-off frequency (Hz)
fs = 376; % Sampling rate (Hz)
n = number fo stored frame
data=[repmat([0;0;1],100,1),repmat([0;0;0;1;1;1],50,1),repmat([0;0;0;0;1;1;1;1;1;1],30,1),repmat([0;0;0;0;0;0;0;0;0;1],30,1)];
b = fir1(n - 1, fc/(fs/2));
filtered = filter(b,1,data);
clf
subplot ( columns ( filtered ), 1, 1)
plot(filtered(:,1),";1/3 - 3;")
subplot ( columns ( filtered ), 1, 2 )
plot(filtered(:,2),";1/2 - 6;")
subplot ( columns ( filtered ), 1, 3 )
plot(filtered(:,3),";6/10 - 10;")
subplot ( columns ( filtered ), 1, 4 )
plot(filtered(:,4),";1/10 - 10;")
bp = 10000 * b; % scaled filter used for PinMame integer math
*/
void core_dmd_pwm_init(core_tDMDPWMState* dmd_state, const int width, const int height, const int filter) {
dmd_state->width = width;
dmd_state->height = height;
dmd_state->rawFrameSize = width * height / 8;
assert(dmd_state->rawFrameSize * 8 == width * height);
switch (filter)
{
case CORE_DMD_PWM_FILTER_DE:
{
static const UINT16 fir_473_15_4[] = { 47, 453, 453, 47 };
dmd_state->nFrames = 4;
dmd_state->fir_weights = fir_473_15_4;
dmd_state->fir_sum = 1000;
dmd_state->fir_size = 4;
}
break;
case CORE_DMD_PWM_FILTER_GTS3:
{
static const UINT16 fir_376_15_24[] = { 8, 19, 44, 91, 168, 274, 405, 552,
699, 830, 928, 981, 981, 928, 830, 699,
552, 405, 274, 168, 91, 44, 19, 8 };
dmd_state->nFrames = 24;
dmd_state->fir_weights = fir_376_15_24;
dmd_state->fir_sum = 9998;
dmd_state->fir_size = 24;
}
break;
default:
assert(0); // Unsupported filter
}
dmd_state->rawFrames = malloc(dmd_state->nFrames * dmd_state->rawFrameSize);
memset(dmd_state->rawFrames, 0, dmd_state->nFrames * dmd_state->rawFrameSize);
dmd_state->shadedFrame = malloc(dmd_state->width * dmd_state->height * sizeof(UINT16));
memset(dmd_state->shadedFrame, 0, dmd_state->width * dmd_state->height * sizeof(UINT16));
dmd_state->nextFrame = 0;
}

void core_dmd_pwm_exit(core_tDMDPWMState* dmd_state) {
free(dmd_state->rawFrames);
dmd_state->rawFrames = NULL;
free(dmd_state->shadedFrame);
dmd_state->shadedFrame = NULL;
}

void core_dmd_submit_frame(core_tDMDPWMState* dmd_state, const UINT8* frame) {
memcpy(dmd_state->rawFrames + dmd_state->nextFrame * dmd_state->rawFrameSize, frame, dmd_state->rawFrameSize);
dmd_state->nextFrame = (dmd_state->nextFrame + 1) % dmd_state->nFrames;
}

void core_dmd_update_pwm(core_tDMDPWMState* dmd_state) {
int ii, jj, kk;

// Apply low pass filter over stored frames
memset(dmd_state->shadedFrame, 0, dmd_state->width * dmd_state->height * sizeof(UINT16));
for (ii = 0; ii < dmd_state->fir_size; ii++) {
const UINT16 frame_weight = dmd_state->fir_weights[ii];
UINT16* line = dmd_state->shadedFrame;
UINT8* frameData = dmd_state->rawFrames + ((dmd_state->nextFrame + (dmd_state->nFrames - 1) + (dmd_state->nFrames - ii)) % dmd_state->nFrames) * dmd_state->rawFrameSize;
for (jj = 0; jj < dmd_state->rawFrameSize; jj++) {
UINT8 data = *frameData++;
for (kk = 0; kk < 8; kk++) {
(*line++) += frame_weight * (UINT16)(data>>7);
data <<= 1;
}
}
}

// Scale down to final shades (note that precision matters and is needed to avoid flickering)
UINT16* line = dmd_state->shadedFrame;
for (ii = 1; ii <= dmd_state->height; ii++)
for (jj = 0; jj < dmd_state->width; jj++) {
UINT16 data = (*line++);
coreGlobals.dotCol[ii][jj] = (UINT8)((255 * (unsigned int)data) / dmd_state->fir_sum);
}
}


//
//
//
Expand Down
21 changes: 21 additions & 0 deletions src/wpc/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,27 @@ extern void core_write_masked_pwm_output_8b(int startIndex, UINT8 bitStates, UIN
extern void core_write_pwm_output_lamp_matrix(int startIndex, UINT8 columns, UINT8 rows, int nCols);
INLINE void core_zero_cross(void) { coreGlobals.lastACZeroCrossTimeStamp = timer_get_time(); }

/*-- DMD PWM integration --*/
typedef struct {
int width; // DMD width
int height; // DMD height
int rawFrameSize; // Size of a raw DMD frame in bytes (width * height / 8)
UINT8* rawFrames; // Buffer for raw frames
UINT16* shadedFrame; // Shaded frame computed from raw frames
int nextFrame; // Position in circular buffer to store next raw frame
int nFrames; // Number of frames to store and consider to create shades (depends on hardware refresh frequency and used PWM patterns)
int fir_size; // Selected filter (depends on hardware refresh frequency and number of stored frames)
UINT16* fir_weights; // Selected filter (depends on hardware refresh frequency and number of stored frames)
UINT16 fir_sum; // Selected filter (depends on hardware refresh frequency and number of stored frames)
} core_tDMDPWMState;
#define CORE_DMD_PWM_FILTER_DE 0 // Data East: 473Hz refresh rate / 15Hz low pass filter / 2 frames PWM pattern
#define CORE_DMD_PWM_FILTER_GTS3 1 // GTS3: 376Hz refresh rate / 15Hz low pass filter / 1,3,6,8,10 frames PWM pattern
#define CORE_DMD_PWM_FILTER_WPC 2 // WPC: 122Hz refresh rate / 15Hz low pass filter / 3 frames PWM pattern
extern void core_dmd_pwm_init(core_tDMDPWMState* dmd_state, const int width, const int height, const int filter);
extern void core_dmd_pwm_exit(core_tDMDPWMState* dmd_state);
extern void core_dmd_submit_frame(core_tDMDPWMState* dmd_state, const UINT8* frame);
extern void core_dmd_update_pwm(core_tDMDPWMState* dmd_state);

extern void core_sound_throttle_adj(int sIn, int *sOut, int buffersize, double samplerate);

/*-- nvram handling --*/
Expand Down
Loading

0 comments on commit 8f8fc5c

Please sign in to comment.