HIFIVE1-VP/platform/src/sysc/mcp_adc.cpp

203 lines
7.8 KiB
C++

/*******************************************************************************
* Copyright (C) 2018 MINRES Technologies GmbH
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*******************************************************************************/
#include <scc/report.h>
#include <sysc/top/mcp_adc.h>
#include <util/ities.h>
namespace sysc {
mcp_3008::mcp_3008(sc_core::sc_module_name nm)
: sysc::mcp_adc(nm, 8)
, last_tx_start(sc_core::SC_ZERO_TIME) {
sck_i.register_nb_transport([this](tlm::tlm_signal_gp<sc_dt::sc_logic> &gp, tlm::tlm_phase &phase,
sc_core::sc_time &delay) -> tlm::tlm_sync_enum { return tlm::TLM_COMPLETED; });
mosi_i.register_nb_transport([this](tlm::tlm_signal_gp<sc_dt::sc_logic> &gp, tlm::tlm_phase &phase,
sc_core::sc_time &delay) -> tlm::tlm_sync_enum {
if (cs_v == sc_dt::Log_0) return receive(gp, phase, delay);
return tlm::TLM_COMPLETED;
});
cs_i.register_nb_transport([this](tlm::tlm_signal_gp<sc_dt::sc_logic> &gp, tlm::tlm_phase &phase,
sc_core::sc_time &delay) -> tlm::tlm_sync_enum {
if (cs_v != sc_dt::Log_0 && gp.get_value() == sc_dt::Log_0) {
idx = 0; // falling edge
rx_bits = 0;
}
cs_v = gp.get_value();
return tlm::TLM_COMPLETED;
});
}
tlm::tlm_sync_enum mcp_3008::receive(tlm::tlm_signal_gp<sc_dt::sc_logic> &gp, tlm::tlm_phase &phase,
sc_core::sc_time &delay) {
gp.get_extension(ext);
if (ext) {
if (ext->start_time != last_tx_start) {
assert(ext->tx.data_bits == 8);
rx_bytes[idx] = bit_sub<0, 8>(ext->tx.m2s_data);
if (idx == 1) do_conversion();
ext->tx.s2m_data = tx_bytes[idx];
ext->tx.s2m_data_valid = true;
idx++;
last_tx_start = ext->start_time;
}
}
return tlm::TLM_COMPLETED;
}
void mcp_3008::do_conversion() {
if (rx_bytes[0] == 0x1) {
auto mode = bit_sub<7, 1>(rx_bytes[1]);
auto channel = bit_sub<4, 3>(rx_bytes[1]);
auto vref = vref_i.read();
if (mode) { // single ended
auto inp = ch_i[channel].read();
auto norm = 1024.0 * inp / vref;
auto res = static_cast<int>(norm);
SCDEBUG(this->name()) << "Converting " << inp << " to " << norm << " as int " << res;
tx_bytes[1] = bit_sub<8, 2>(res);
tx_bytes[2] = bit_sub<0, 8>(res);
} else {
tx_bytes[1] = 0;
tx_bytes[2] = 0;
}
}
}
mcp_3208::mcp_3208(sc_core::sc_module_name nm)
: sysc::mcp_adc(nm, 8)
, ext(nullptr)
, last_tx_start(sc_core::SC_ZERO_TIME) {
sck_i.register_nb_transport([this](tlm::tlm_signal_gp<sc_dt::sc_logic> &gp, tlm::tlm_phase &phase,
sc_core::sc_time &delay) -> tlm::tlm_sync_enum {
auto ret = tlm::TLM_COMPLETED;
if (cs_v == sc_dt::Log_0) ret = receive(gp, phase, delay);
sck_v = gp.get_value();
return ret;
});
mosi_i.register_nb_transport([this](tlm::tlm_signal_gp<sc_dt::sc_logic> &gp, tlm::tlm_phase &phase,
sc_core::sc_time &delay) -> tlm::tlm_sync_enum {
mosi_v = gp.get_value();
return tlm::TLM_COMPLETED;
});
cs_i.register_nb_transport([this](tlm::tlm_signal_gp<sc_dt::sc_logic> &gp, tlm::tlm_phase &phase,
sc_core::sc_time &delay) -> tlm::tlm_sync_enum {
if (cs_v != sc_dt::Log_0 && gp.get_value() == sc_dt::Log_0) { // falling edge of CS
idx = 0;
rx_bits = byte_offs = 0;
bit_offs = 7;
}
cs_v = gp.get_value();
return tlm::TLM_COMPLETED;
});
SC_METHOD(sample_inputs);
sensitive << clk_sample_evt;
}
tlm::tlm_sync_enum mcp_3208::receive(tlm::tlm_signal_gp<sc_dt::sc_logic> &gp, tlm::tlm_phase &phase,
sc_core::sc_time &delay) {
gp.get_extension(ext);
if (ext) {
if (ext->start_time != last_tx_start) {
assert(ext->tx.data_bits == 8);
if (ext->tx.m2s_data_valid) {
rx_bytes[idx] = bit_sub<0, 8>(ext->tx.m2s_data);
if (idx == 1) do_conversion();
ext->tx.s2m_data = tx_bytes[idx];
ext->tx.s2m_data_valid = true;
last_tx_start = ext->start_time;
idx++;
}
}
} else if (gp.get_value() == sc_dt::SC_LOGIC_1 && sck_v == sc_dt::SC_LOGIC_0) // sample an rising edge
clk_sample_evt.notify(sc_core::SC_ZERO_TIME);
return tlm::TLM_COMPLETED;
}
void mcp_3208::sample_inputs() {
if (byte_offs >= 3) return;
if (bit_offs == 7) {
rx_bytes[byte_offs] = 0;
if (byte_offs == 0) tx_bytes[0] = tx_bytes[1] = tx_bytes[2] = 0;
}
auto mask = 1 << bit_offs;
if (mosi_v == sc_dt::SC_LOGIC_1) rx_bytes[byte_offs] |= mask;
miso_o.write_now(tx_bytes[byte_offs] & mask ? sc_dt::SC_LOGIC_1 : sc_dt::SC_LOGIC_0);
// increment counters
if (bit_offs == 0) {
bit_offs = 7;
byte_offs++;
} else
bit_offs--;
// sample if in the middle of second byte
if (byte_offs == 1 && bit_offs == 4) do_conversion();
}
void mcp_3208::do_conversion() {
if (rx_bytes[0] & 0x4) {
auto mode = bit_sub<1, 1>(rx_bytes[0]);
auto channel = bit_sub<0, 1>(rx_bytes[0]) * 4 + bit_sub<6, 2>(rx_bytes[1]);
auto vref = vref_i.read();
if (mode) { // single ended
auto inp = ch_i[channel].read();
auto norm = 4096.0 * inp / vref;
auto res = static_cast<int>(norm);
SCDEBUG(this->name()) << "Converting channel " << channel << " " << inp << "V to " << norm << " as int "
<< res;
tx_bytes[1] = bit_sub<8, 4>(res);
tx_bytes[2] = bit_sub<0, 8>(res);
} else {
tx_bytes[1] = 0;
tx_bytes[2] = 0;
}
}
}
template <>
std::unique_ptr<mcp_adc> mcp_adc::create<mcp_3008>(sc_core::sc_module_name nm) {
auto *res = new mcp_3008(nm);
return std::unique_ptr<mcp_adc>(res);
}
template <>
std::unique_ptr<mcp_adc> mcp_adc::create<mcp_3208>(sc_core::sc_module_name nm) {
auto *res = new mcp_3208(nm);
return std::unique_ptr<mcp_adc>(res);
}
} /* namespace sysc */