summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author AJR <ajrhacker@users.noreply.github.com>2019-11-03 16:31:47 -0500
committer AJR <ajrhacker@users.noreply.github.com>2019-11-03 16:33:47 -0500
commited087f295e4132c9686369a5b03fe3d5486d1b95 (patch)
treeb640fff8f7e0f06cd7dfd1172de216d209110bff
parent2435f3cfed5317153cce3a94ccf7507ab2898bbe (diff)
st2xxx: Add base timers and some of the LCD registers; fix IRR enable (nw)
-rw-r--r--src/devices/cpu/m6502/st2204.cpp75
-rw-r--r--src/devices/cpu/m6502/st2204.h15
-rw-r--r--src/devices/cpu/m6502/st2205u.cpp105
-rw-r--r--src/devices/cpu/m6502/st2205u.h17
-rw-r--r--src/devices/cpu/m6502/st2xxx.cpp41
-rw-r--r--src/devices/cpu/m6502/st2xxx.h18
-rw-r--r--src/mame/drivers/inteladv.cpp5
7 files changed, 271 insertions, 5 deletions
diff --git a/src/devices/cpu/m6502/st2204.cpp b/src/devices/cpu/m6502/st2204.cpp
index 7f70fd70b89..cad69651872 100644
--- a/src/devices/cpu/m6502/st2204.cpp
+++ b/src/devices/cpu/m6502/st2204.cpp
@@ -35,9 +35,20 @@ st2204_device::st2204_device(const machine_config &mconfig, const char *tag, dev
address_map_constructor(FUNC(st2204_device::int_map), this),
26, // logical; only 23 address lines are brought out
0x0f7f)
+ , m_bten(0)
+ , m_btsr(0)
+ , m_base_timer{0}
{
}
+template<int N>
+TIMER_CALLBACK_MEMBER(st2204_device::bt_interrupt)
+{
+ m_btsr |= 1 << N;
+ m_ireq |= 0x020;
+ update_irq_state();
+}
+
void st2204_device::device_start()
{
std::unique_ptr<mi_st2204> intf = std::make_unique<mi_st2204>();
@@ -49,14 +60,26 @@ void st2204_device::device_start()
intf->drr = 0;
intf->irq_service = false;
+ m_base_timer[0] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2204_device::bt_interrupt<0>), this));
+ m_base_timer[1] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2204_device::bt_interrupt<1>), this));
+ m_base_timer[2] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2204_device::bt_interrupt<2>), this));
+ m_base_timer[3] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2204_device::bt_interrupt<3>), this));
+ m_base_timer[4] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2204_device::bt_interrupt<4>), this));
+
save_item(NAME(m_pdata));
save_item(NAME(m_pctrl));
save_item(NAME(m_psel));
save_item(NAME(m_pfun));
+ save_item(NAME(m_bten));
+ save_item(NAME(m_btsr));
save_item(NAME(m_sys));
save_item(NAME(m_pmcr));
save_item(NAME(m_ireq));
save_item(NAME(m_iena));
+ save_item(NAME(m_lssa));
+ save_item(NAME(m_lvpw));
+ save_item(NAME(m_lxmax));
+ save_item(NAME(m_lymax));
save_item(NAME(intf->irr_enable));
save_item(NAME(intf->irr));
save_item(NAME(intf->prr));
@@ -83,7 +106,13 @@ void st2204_device::device_start()
state_add(ST_PDL, "PDL", m_pdata[6]);
state_add(ST_PCL, "PCL", m_pctrl[6]);
state_add(ST_PMCR, "PMCR", m_pmcr);
+ state_add<u8>(ST_BTEN, "BTEN", [this]() { return m_bten; }, [this](u8 data) { bten_w(data); }).mask(0x1f);
+ state_add(ST_BTSR, "BTSR", m_btsr).mask(0x1f);
state_add<u8>(ST_SYS, "SYS", [this]() { return m_sys; }, [this](u8 data) { sys_w(data); });
+ state_add(ST_LSSA, "LSSA", m_lssa);
+ state_add(ST_LVPW, "LVPW", m_lvpw);
+ state_add(ST_LXMAX, "LXMAX", m_lxmax);
+ state_add(ST_LYMAX, "LYMAX", m_lymax);
}
void st2204_device::device_reset()
@@ -95,6 +124,9 @@ void st2204_device::device_reset()
m.irr = 0;
m.prr = 0;
m.drr = 0;
+
+ bten_w(0);
+ m_btsr = 0;
}
u8 st2204_device::mi_st2204::pread(u16 adr)
@@ -165,6 +197,40 @@ void st2204_device::pmcr_w(u8 data)
m_pmcr = data;
}
+u8 st2204_device::bten_r()
+{
+ return m_bten;
+}
+
+void st2204_device::bten_w(u8 data)
+{
+ for (int n = 0; n < 5; n++)
+ {
+ if (BIT(data, n) && !BIT(m_bten, n))
+ {
+ // 2 Hz, 8 Hz, 64 Hz, 256 Hz, 2048 Hz
+ attotime period = attotime::from_hz(2 << ((n & 1) * 2 + (n >> 1) * 5));
+ m_base_timer[n]->adjust(period, 0, period);
+ }
+ else if (!BIT(data, n) && BIT(m_bten, n))
+ m_base_timer[n]->adjust(attotime::never);
+ }
+
+ m_bten = data & 0x1f;
+}
+
+u8 st2204_device::btsr_r()
+{
+ return m_btsr;
+}
+
+void st2204_device::btsr_w(u8 data)
+{
+ // Only bit 7 has any effect
+ if (BIT(data, 7))
+ m_btsr = 0;
+}
+
u8 st2204_device::sys_r()
{
return m_sys | 0x01;
@@ -173,7 +239,7 @@ u8 st2204_device::sys_r()
void st2204_device::sys_w(u8 data)
{
m_sys = data;
- downcast<mi_st2204 &>(*mintf).irr_enable = BIT(data, 2);
+ downcast<mi_st2204 &>(*mintf).irr_enable = BIT(data, 1);
}
u8 st2204_device::irr_r()
@@ -258,6 +324,8 @@ void st2204_device::int_map(address_map &map)
map(0x000d, 0x000d).rw(FUNC(st2204_device::pfc_r), FUNC(st2204_device::pfc_w));
map(0x000e, 0x000e).rw(FUNC(st2204_device::pfd_r), FUNC(st2204_device::pfd_w));
map(0x000f, 0x000f).rw(FUNC(st2204_device::pmcr_r), FUNC(st2204_device::pmcr_w));
+ map(0x0020, 0x0020).rw(FUNC(st2204_device::bten_r), FUNC(st2204_device::bten_w));
+ map(0x0021, 0x0021).rw(FUNC(st2204_device::btsr_r), FUNC(st2204_device::btsr_w));
map(0x0030, 0x0030).rw(FUNC(st2204_device::sys_r), FUNC(st2204_device::sys_w));
map(0x0031, 0x0031).rw(FUNC(st2204_device::irr_r), FUNC(st2204_device::irr_w));
map(0x0032, 0x0032).rw(FUNC(st2204_device::prrl_r), FUNC(st2204_device::prrl_w));
@@ -268,6 +336,11 @@ void st2204_device::int_map(address_map &map)
map(0x003d, 0x003d).rw(FUNC(st2204_device::ireqh_r), FUNC(st2204_device::ireqh_w));
map(0x003e, 0x003e).rw(FUNC(st2204_device::ienal_r), FUNC(st2204_device::ienal_w));
map(0x003f, 0x003f).rw(FUNC(st2204_device::ienah_r), FUNC(st2204_device::ienah_w));
+ map(0x0040, 0x0040).w(FUNC(st2204_device::lssal_w));
+ map(0x0041, 0x0041).w(FUNC(st2204_device::lssah_w));
+ map(0x0042, 0x0042).w(FUNC(st2204_device::lvpw_w));
+ map(0x0043, 0x0043).rw(FUNC(st2204_device::lxmax_r), FUNC(st2204_device::lxmax_w));
+ map(0x0044, 0x0044).rw(FUNC(st2204_device::lymax_r), FUNC(st2204_device::lymax_w));
map(0x004c, 0x004c).rw(FUNC(st2204_device::pl_r), FUNC(st2204_device::pl_w));
map(0x004e, 0x004e).w(FUNC(st2204_device::pcl_w));
map(0x0080, 0x27ff).ram(); // 10K internal SRAM; extent of mapping guessed
diff --git a/src/devices/cpu/m6502/st2204.h b/src/devices/cpu/m6502/st2204.h
index 4ec61636d65..d88825a8b7f 100644
--- a/src/devices/cpu/m6502/st2204.h
+++ b/src/devices/cpu/m6502/st2204.h
@@ -16,6 +16,11 @@
class st2204_device : public st2xxx_device
{
public:
+ enum {
+ ST_BTEN = ST_LYMAX + 1,
+ ST_BTSR
+ };
+
st2204_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
protected:
@@ -44,6 +49,8 @@ private:
u16 drr;
};
+ template<int N> TIMER_CALLBACK_MEMBER(bt_interrupt);
+
u8 sys_r();
void sys_w(u8 data);
u8 irr_r();
@@ -58,6 +65,10 @@ private:
void drrh_w(u8 data);
u8 pmcr_r();
void pmcr_w(u8 data);
+ u8 bten_r();
+ void bten_w(u8 data);
+ u8 btsr_r();
+ void btsr_w(u8 data);
u8 pmem_r(offs_t offset);
void pmem_w(offs_t offset, u8 data);
@@ -65,6 +76,10 @@ private:
void dmem_w(offs_t offset, u8 data);
void int_map(address_map &map);
+
+ u8 m_bten;
+ u8 m_btsr;
+ emu_timer *m_base_timer[5];
};
DECLARE_DEVICE_TYPE(ST2204, st2204_device)
diff --git a/src/devices/cpu/m6502/st2205u.cpp b/src/devices/cpu/m6502/st2205u.cpp
index 7084eb632c4..2dff95733f0 100644
--- a/src/devices/cpu/m6502/st2205u.cpp
+++ b/src/devices/cpu/m6502/st2205u.cpp
@@ -36,10 +36,22 @@ st2205u_device::st2205u_device(const machine_config &mconfig, const char *tag, d
address_map_constructor(FUNC(st2205u_device::int_map), this),
26, // logical; only 23 address lines are brought out
0xdfff)
+ , m_bten(0)
+ , m_btreq(0)
+ , m_btc(0)
+ , m_base_timer{0}
, m_lvctr(0)
{
}
+template<int N>
+TIMER_CALLBACK_MEMBER(st2205u_device::bt_interrupt)
+{
+ m_btreq |= 1 << N;
+ m_ireq |= 0x0020;
+ update_irq_state();
+}
+
void st2205u_device::device_start()
{
std::unique_ptr<mi_st2205u> intf = std::make_unique<mi_st2205u>();
@@ -52,16 +64,32 @@ void st2205u_device::device_start()
intf->brr = 0;
intf->irq_service = false;
+ m_base_timer[0] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2205u_device::bt_interrupt<0>), this));
+ m_base_timer[1] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2205u_device::bt_interrupt<1>), this));
+ m_base_timer[2] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2205u_device::bt_interrupt<2>), this));
+ m_base_timer[3] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2205u_device::bt_interrupt<3>), this));
+ m_base_timer[4] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2205u_device::bt_interrupt<4>), this));
+ m_base_timer[5] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2205u_device::bt_interrupt<5>), this));
+ m_base_timer[6] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2205u_device::bt_interrupt<6>), this));
+ m_base_timer[7] = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(st2205u_device::bt_interrupt<7>), this));
+
intf->ram = make_unique_clear<u8[]>(0x8000);
save_item(NAME(m_pdata));
save_item(NAME(m_pctrl));
save_item(NAME(m_psel));
save_item(NAME(m_pfun));
+ save_item(NAME(m_bten));
+ save_item(NAME(m_btreq));
+ save_item(NAME(m_btc));
save_item(NAME(m_sys));
save_item(NAME(m_pmcr));
save_item(NAME(m_ireq));
save_item(NAME(m_iena));
+ save_item(NAME(m_lssa));
+ save_item(NAME(m_lvpw));
+ save_item(NAME(m_lxmax));
+ save_item(NAME(m_lymax));
save_item(NAME(m_lvctr));
save_item(NAME(intf->irr_enable));
save_item(NAME(intf->irr));
@@ -93,6 +121,13 @@ void st2205u_device::device_start()
state_add(ST_PCL, "PCL", m_pctrl[6]);
state_add(ST_PMCR, "PMCR", m_pmcr);
state_add<u8>(ST_SYS, "SYS", [this]() { return m_sys; }, [this](u8 data) { sys_w(data); }).mask(0xfe);
+ state_add<u8>(ST_BTEN, "BTEN", [this]() { return m_bten; }, [this](u8 data) { bten_w(data); });
+ state_add(ST_BTREQ, "BTREQ", m_btreq);
+ state_add(ST_BTC, "BTC", m_btc);
+ state_add(ST_LSSA, "LSSA", m_lssa);
+ state_add(ST_LVPW, "LVPW", m_lvpw);
+ state_add(ST_LXMAX, "LXMAX", m_lxmax);
+ state_add(ST_LYMAX, "LYMAX", m_lymax);
state_add(ST_LVCTR, "LVCTR", m_lvctr).mask(0x0f);
}
@@ -106,6 +141,10 @@ void st2205u_device::device_reset()
m.prr = 0;
m.drr = 0;
+ bten_w(0);
+ m_btreq = 0;
+ m_btc = 0;
+
m_lvctr = 0;
}
@@ -315,7 +354,63 @@ u8 st2205u_device::sys_r()
void st2205u_device::sys_w(u8 data)
{
m_sys = data & 0xfe;
- downcast<mi_st2205u &>(*mintf).irr_enable = BIT(data, 2);
+ downcast<mi_st2205u &>(*mintf).irr_enable = BIT(data, 1);
+}
+
+u8 st2205u_device::bten_r()
+{
+ return m_bten;
+}
+
+void st2205u_device::bten_w(u8 data)
+{
+ if (BIT(data, 0) && !BIT(m_bten, 0))
+ m_base_timer[0]->adjust(attotime::from_hz(2), 0, attotime::from_hz(2));
+ else if (!BIT(data, 0) && BIT(m_bten, 0))
+ m_base_timer[0]->adjust(attotime::never);
+
+ for (int n = 1; n <= 5; n++)
+ {
+ if (BIT(data, n) && !BIT(m_bten, n))
+ m_base_timer[n]->adjust(attotime::from_hz(16 << n), 0, attotime::from_hz(16 << n));
+ else if (!BIT(data, n) && BIT(m_bten, n))
+ m_base_timer[n]->adjust(attotime::never);
+ }
+
+ if (BIT(data, 6) && !BIT(m_bten, 6))
+ m_base_timer[6]->adjust(attotime::from_hz(2048), 0, attotime::from_hz(2048));
+ else if (!BIT(data, 6) && BIT(m_bten, 6))
+ m_base_timer[6]->adjust(attotime::never);
+
+ if (BIT(data, 7) && !BIT(m_bten, 7))
+ {
+ attotime period = attotime::from_hz(8192) * (m_btc ? m_btc : 256);
+ m_base_timer[7]->adjust(period, 0, period);
+ }
+ else if (!BIT(data, 7) && BIT(m_bten, 7))
+ m_base_timer[7]->adjust(attotime::never);
+
+ m_bten = data;
+}
+
+u8 st2205u_device::btreq_r()
+{
+ return m_btreq;
+}
+
+void st2205u_device::btclr_w(u8 data)
+{
+ m_btreq &= ~data;
+}
+
+u8 st2205u_device::btc_r()
+{
+ return m_btc;
+}
+
+void st2205u_device::btc_w(u8 data)
+{
+ m_btc = data;
}
u8 st2205u_device::lvctr_r()
@@ -376,6 +471,9 @@ void st2205u_device::int_map(address_map &map)
map(0x0008, 0x000d).rw(FUNC(st2205u_device::pctrl_r), FUNC(st2205u_device::pctrl_w));
map(0x000e, 0x000e).rw(FUNC(st2205u_device::pfc_r), FUNC(st2205u_device::pfc_w));
map(0x000f, 0x000f).rw(FUNC(st2205u_device::pfd_r), FUNC(st2205u_device::pfd_w));
+ map(0x002a, 0x002a).rw(FUNC(st2205u_device::bten_r), FUNC(st2205u_device::bten_w));
+ map(0x002b, 0x002b).rw(FUNC(st2205u_device::btreq_r), FUNC(st2205u_device::btclr_w));
+ map(0x002c, 0x002c).rw(FUNC(st2205u_device::btc_r), FUNC(st2205u_device::btc_w));
map(0x0030, 0x0030).rw(FUNC(st2205u_device::irrl_r), FUNC(st2205u_device::irrl_w));
map(0x0031, 0x0031).rw(FUNC(st2205u_device::irrh_r), FUNC(st2205u_device::irrh_w));
map(0x0032, 0x0032).rw(FUNC(st2205u_device::prrl_r), FUNC(st2205u_device::prrl_w));
@@ -390,6 +488,11 @@ void st2205u_device::int_map(address_map &map)
map(0x003d, 0x003d).rw(FUNC(st2205u_device::ireqh_r), FUNC(st2205u_device::ireqh_w));
map(0x003e, 0x003e).rw(FUNC(st2205u_device::ienal_r), FUNC(st2205u_device::ienal_w));
map(0x003f, 0x003f).rw(FUNC(st2205u_device::ienah_r), FUNC(st2205u_device::ienah_w));
+ map(0x0040, 0x0040).w(FUNC(st2205u_device::lssal_w));
+ map(0x0041, 0x0041).w(FUNC(st2205u_device::lssah_w));
+ map(0x0042, 0x0042).w(FUNC(st2205u_device::lvpw_w));
+ map(0x0043, 0x0043).rw(FUNC(st2205u_device::lxmax_r), FUNC(st2205u_device::lxmax_w));
+ map(0x0044, 0x0044).rw(FUNC(st2205u_device::lymax_r), FUNC(st2205u_device::lymax_w));
map(0x004e, 0x004e).rw(FUNC(st2205u_device::pl_r), FUNC(st2205u_device::pl_w));
map(0x004f, 0x004f).rw(FUNC(st2205u_device::pcl_r), FUNC(st2205u_device::pcl_w));
map(0x0057, 0x0057).rw(FUNC(st2205u_device::lvctr_r), FUNC(st2205u_device::lvctr_w));
diff --git a/src/devices/cpu/m6502/st2205u.h b/src/devices/cpu/m6502/st2205u.h
index 15ab962b8f3..2a04bc67be7 100644
--- a/src/devices/cpu/m6502/st2205u.h
+++ b/src/devices/cpu/m6502/st2205u.h
@@ -17,7 +17,10 @@ class st2205u_device : public st2xxx_device
{
public:
enum {
- ST_BRR = ST_IENA + 1,
+ ST_BTEN = ST_LYMAX + 1,
+ ST_BTREQ,
+ ST_BTC,
+ ST_BRR,
ST_LVCTR
};
@@ -55,6 +58,8 @@ private:
std::unique_ptr<u8[]> ram;
};
+ template<int N> TIMER_CALLBACK_MEMBER(bt_interrupt);
+
u8 irrl_r();
void irrl_w(u8 data);
u8 irrh_r();
@@ -75,6 +80,12 @@ private:
void sys_w(u8 data);
u8 pmcr_r();
void pmcr_w(u8 data);
+ u8 bten_r();
+ void bten_w(u8 data);
+ u8 btreq_r();
+ void btclr_w(u8 data);
+ u8 btc_r();
+ void btc_w(u8 data);
u8 lvctr_r();
void lvctr_w(u8 data);
@@ -89,6 +100,10 @@ private:
void int_map(address_map &map);
+ u8 m_bten;
+ u8 m_btreq;
+ u8 m_btc;
+ emu_timer *m_base_timer[8];
u8 m_lvctr;
};
diff --git a/src/devices/cpu/m6502/st2xxx.cpp b/src/devices/cpu/m6502/st2xxx.cpp
index 711360b355f..8905e9942d4 100644
--- a/src/devices/cpu/m6502/st2xxx.cpp
+++ b/src/devices/cpu/m6502/st2xxx.cpp
@@ -76,6 +76,12 @@ void st2xxx_device::device_reset()
// reset miscellaneous registers
m_sys = 0;
+
+ // reset LCDC registers
+ m_lssa = 0;
+ m_lvpw = 0;
+ m_lxmax = 0;
+ m_lymax = 0;
}
std::unique_ptr<util::disasm_interface> st2xxx_device::create_disassembler()
@@ -249,4 +255,39 @@ void st2xxx_device::ienah_w(u8 data)
update_irq_state();
}
+void st2xxx_device::lssal_w(u8 data)
+{
+ m_lssa = (m_lssa & 0xff00) | data;
+}
+
+void st2xxx_device::lssah_w(u8 data)
+{
+ m_lssa = (m_lssa & 0x00ff) | (u16(data) << 8);
+}
+
+void st2xxx_device::lvpw_w(u8 data)
+{
+ m_lvpw = data;
+}
+
+u8 st2xxx_device::lxmax_r()
+{
+ return m_lxmax;
+}
+
+void st2xxx_device::lxmax_w(u8 data)
+{
+ m_lxmax = data;
+}
+
+u8 st2xxx_device::lymax_r()
+{
+ return m_lymax;
+}
+
+void st2xxx_device::lymax_w(u8 data)
+{
+ m_lymax = data;
+}
+
#include "cpu/m6502/st2xxx.hxx"
diff --git a/src/devices/cpu/m6502/st2xxx.h b/src/devices/cpu/m6502/st2xxx.h
index 2d328be54f0..b39b63d5671 100644
--- a/src/devices/cpu/m6502/st2xxx.h
+++ b/src/devices/cpu/m6502/st2xxx.h
@@ -37,7 +37,11 @@ public:
ST_PRR,
ST_DRR,
ST_IREQ,
- ST_IENA
+ ST_IENA,
+ ST_LSSA,
+ ST_LVPW,
+ ST_LXMAX,
+ ST_LYMAX
};
auto in_pa_callback() { return m_in_port_cb[0].bind(); }
@@ -107,6 +111,14 @@ protected:
u8 ienah_r();
void ienah_w(u8 data);
+ void lssal_w(u8 data);
+ void lssah_w(u8 data);
+ void lvpw_w(u8 data);
+ u8 lxmax_r();
+ void lxmax_w(u8 data);
+ u8 lymax_r();
+ void lymax_w(u8 data);
+
#define O(o) void o ## _full(); void o ## _partial()
O(brk_st_imp);
@@ -129,6 +141,10 @@ protected:
u16 m_iena;
const u16 m_ireq_mask;
u8 m_sys;
+ u16 m_lssa;
+ u8 m_lvpw;
+ u8 m_lxmax;
+ u8 m_lymax;
};
#endif // MAME_CPU_M6502_ST2XXX_H
diff --git a/src/mame/drivers/inteladv.cpp b/src/mame/drivers/inteladv.cpp
index a62436236fe..169f69eb304 100644
--- a/src/mame/drivers/inteladv.cpp
+++ b/src/mame/drivers/inteladv.cpp
@@ -4,6 +4,9 @@
VTech Intelligence Advance E/R Lerncomputer
+ TODO: dyndesk writes to low memory go to external RAM instead? It also
+ eventually runs off into nonexistent memory.
+
***************************************************************************/
#include "emu.h"
@@ -36,7 +39,7 @@ void inteladv_state::inteladv_map(address_map &map)
void inteladv_state::dyndesk_map(address_map &map)
{
- map(0x000000, 0x1fffff).rom().region("maincpu", 0).nopw(); // writes to low memory go to external RAM instead?
+ map(0x000000, 0x1fffff).rom().region("maincpu", 0).nopw();
}
static INPUT_PORTS_START( inteladv )