OXYGENHUB
◆ main openmsx / src / memory / MSXHBI55.cc
6.0 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
/* Submitted By: Albert Beevendorp (bifimsx)
2
 *
3
 * Some Sony MSX machines have built-in firmware
4
 * containing an address 'book', memo and scheduler and a
5
 * few of these can store these data on a data cartridge.
6
 *
7
 * In Basic it's possible to use it with the "CAT:" device.
8
 *
9
 * I'm not sure if there are more types, though the HBI-55
10
 * is a 4KB SRAM containing cartridge accessed by I/O
11
 * ports connected to a 8255 chip:
12
 *
13
 * B0 = LSB address
14
 * B1 = MSB address (D7-D6 = 01 write, 11 read)
15
 * B2 = Data port
16
 * B3 = Access control
17
 *
18
 * Sample basic program:
19
 *     10 FOR I=0 TO 4095: OUT &HB3,128: OUT &HB0,I MOD 256:
20
 *        OUT &HB1,64 OR I\256: OUT &HB2,I MOD 256: NEXT I
21
 *     20 FOR I=0 TO 4095:OUT &HB3,128-9*(I<>0): OUT &HB0,I MOD 256:
22
 *        OUT &HB1,192 OR I\256: IF I MOD 256=INP(&HB2) THEN NEXT
23
 *        ELSE PRINT "Error comparing byte:";I: END
24
 *     30 PRINT "Done!"
25
 * Update:
26
 *   This test-program is not correct, see the link below for improved
27
 *   version(s).
28
 *
29
 * -----
30
 *
31
 * Improved code mostly copied from blueMSX, many thanks to Daniel Vik.
32
 *   http://cvs.sourceforge.net/viewcvs.py/bluemsx/blueMSX/Src/Memory/romMapperSonyHBI55.c
33
 * Update:
34
 *   Later the implementation was simplified quite a bit by replacing internal
35
 *   state with looking at the current PPI output values. This is closer to how
36
 *   the real hardware works.
37
 *
38
 * When only one of the nibbles is written (actually when one is set as
39
 * input the other as output), the other nibble gets the value of the
40
 * last time that nibble was written. Thanks to Laurens Holst for
41
 * investigating this. See this bug report for more details:
42
 *   https://sourceforge.net/p/openmsx/bugs/536/
43
 * Update:
44
 *   This behavior is actually undefined: it writes a 'floating value'.
45
 *   This value happens to (mostly) be the last written value, at least if
46
 *   there's not too much time between the writes. Nevertheless we currently
47
 *   emulate it like this.
48
 *
49
 * For more details see the bug report(s) at:
50
 *   https://github.com/openMSX/openMSX/issues/927
51
 * Especially see grauw's (updated) test programs (2022/02/20).
52
 *
53
 * See also the (updated) documentation on:
54
 *   http://map.grauw.nl/resources/hbi55.php
55
 * And the service manual (contains electrical schema):
56
 *   http://map.grauw.nl/resources/memory/sony_hbi-55_sm.pdf
57
 */
58
59
#include "MSXHBI55.hh"
60
#include "GlobalSettings.hh"
61
#include "serialize.hh"
62
63
namespace openmsx {
64
65
// MSXDevice
66
67
MSXHBI55::MSXHBI55(const DeviceConfig& config)
68
	: MSXDevice(config)
69
	, i8255(*this, getCurrentTime(), config.getGlobalSettings().getInvalidPpiModeSetting())
70
	, sram(getName() + " SRAM", 0x1000, config)
71
{
72
	reset(getCurrentTime());
73
}
74
75
void MSXHBI55::reset(EmuTime time)
76
{
77
	lastC = 255; // hack
78
	i8255.reset(time);
79
}
80
81
byte MSXHBI55::readIO(uint16_t port, EmuTime time)
82
{
83
	return i8255.read(port & 0x03, time);
84
}
85
86
byte MSXHBI55::peekIO(uint16_t port, EmuTime time) const
87
{
88
	return i8255.peek(port & 0x03, time);
89
}
90
91
void MSXHBI55::writeIO(uint16_t port, byte value, EmuTime time)
92
{
93
	i8255.write(port & 0x03, value, time);
94
}
95
96
97
// I8255Interface
98
99
byte MSXHBI55::readA(EmuTime time)
100
{
101
	return peekA(time);
102
}
103
byte MSXHBI55::peekA(EmuTime /*time*/) const
104
{
105
	return 255; // TODO check this
106
}
107
byte MSXHBI55::readB(EmuTime time)
108
{
109
	return peekB(time);
110
}
111
byte MSXHBI55::peekB(EmuTime /*time*/) const
112
{
113
	return 255; // TODO check this
114
}
115
uint4_t MSXHBI55::readC0(EmuTime time)
116
{
117
	return peekC0(time);
118
}
119
uint4_t MSXHBI55::peekC0(EmuTime /*time*/) const
120
{
121
	return readStuff() & 0x0F;
122
}
123
uint4_t MSXHBI55::readC1(EmuTime time)
124
{
125
	return peekC1(time);
126
}
127
uint4_t MSXHBI55::peekC1(EmuTime /*time*/) const
128
{
129
	return readStuff() >> 4;
130
}
131
132
void MSXHBI55::writeA(byte /*value*/, EmuTime /*time*/)
133
{
134
	writeStuff();
135
}
136
void MSXHBI55::writeB(byte /*value*/, EmuTime /*time*/)
137
{
138
	writeStuff();
139
}
140
void MSXHBI55::writeC0(uint4_t value, EmuTime /*time*/)
141
{
142
	lastC = (lastC & 0xf0) | value; // hack
143
	writeStuff();
144
}
145
void MSXHBI55::writeC1(uint4_t value, EmuTime /*time*/)
146
{
147
	lastC = byte((lastC & 0x0f) | (value << 4)); // hack
148
	writeStuff();
149
}
150
151
void MSXHBI55::writeStuff()
152
{
153
	byte B = i8255.getPortB();
154
	if ((B & 0x70) != 0x40) {
155
		// /CE of RAM chip(s) not active
156
		return;
157
	}
158
159
	if (B & 0x80) {
160
		// read, do nothing
161
	} else {
162
		// write
163
		byte A = i8255.getPortA();
164
		unsigned addr = ((B & 0x0f) << 8) | A;
165
		// In the normal case port C should be programmed as an output,
166
		// and then we can get the value like this:
167
		//     byte C = i8255.getPortC();
168
		// When port C is (partially) set as input, we technically use a
169
		// 'floating value'. Hack: experiments have shown that we more
170
		// closely _approximate_ this (invalid) case by using the last
171
		// written (output-)value.
172
		byte C = lastC;
173
		sram.write(addr, C);
174
	}
175
}
176
177
byte MSXHBI55::readStuff() const
178
{
179
	byte B = i8255.getPortB();
180
	if ((B & 0x70) != 0x40) {
181
		// /CE of RAM chip(s) not active
182
		return lastC; // actually floating value
183
	}
184
185
	if (B & 0x80) {
186
		// read
187
		byte A = i8255.getPortA();
188
		unsigned addr = ((B & 0x0f) << 8) | A;
189
		return sram[addr];
190
	} else {
191
		// write
192
		// Normally this shouldn't occur: when writing, port C should be
193
		// programmed as an output, and then this method won't get called.
194
		return lastC; // actually floating value
195
	}
196
}
197
198
// version 1: initial version
199
// version 2: removed 'readAddress', 'writeAddress', 'addressLatch', 'writeLatch', 'mode'
200
//                    these are replaced by i8255.getPort{A,B,C}().
201
//            introduced 'lastC'
202
//                    This is a hack: it's used instead of i8255.getPortC().
203
//                    This more or less maps to the old 'writeLatch' variable.
204
template<typename Archive>
205
void MSXHBI55::serialize(Archive& ar, unsigned version)
206
{
207
	ar.template serializeBase<MSXDevice>(*this);
208
	ar.serialize("i8255", i8255,
209
	             "SRAM",  sram);
210
	if (ar.versionAtLeast(version, 2)) {
211
		ar.serialize("lastC", lastC);
212
	} else {
213
		assert(Archive::IS_LOADER);
214
		byte writeLatch = 0;
215
		ar.serialize("writeLatch", writeLatch);
216
		lastC = writeLatch;
217
	}
218
}
219
INSTANTIATE_SERIALIZE_METHODS(MSXHBI55);
220
REGISTER_MSXDEVICE(MSXHBI55, "MSXHBI55");
221
222
} // namespace openmsx