2018-11-08 13:31:28 +01:00
|
|
|
/*******************************************************************************
|
|
|
|
* Copyright (C) 2017, 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 "sysc/SiFive/pwm.h"
|
|
|
|
|
|
|
|
#include "scc/utilities.h"
|
|
|
|
#include "sysc/SiFive/gen/pwm_regs.h"
|
|
|
|
|
|
|
|
using namespace sysc;
|
|
|
|
using namespace sc_core;
|
|
|
|
|
|
|
|
pwm::pwm(sc_core::sc_module_name nm)
|
|
|
|
: sc_core::sc_module(nm)
|
|
|
|
, tlm_target<>(clk)
|
|
|
|
, NAMED(clk_i)
|
|
|
|
, NAMED(rst_i)
|
|
|
|
, NAMED(cmpgpio_o, 4)
|
|
|
|
, NAMED(cmpip_o, 4)
|
|
|
|
, NAMEDD(regs, pwm_regs)
|
|
|
|
, current_cnt(0)
|
|
|
|
, last_cnt_update() {
|
|
|
|
regs->registerResources(*this);
|
|
|
|
|
|
|
|
regs->pwmcfg.set_write_cb(
|
2019-04-11 07:40:02 +02:00
|
|
|
[this](const scc::sc_register<uint32_t> ®, const uint32_t &data, sc_core::sc_time d) -> bool {
|
2018-11-08 13:31:28 +01:00
|
|
|
if (d.value()) wait(d);
|
|
|
|
reg.put(data);
|
|
|
|
update_counter();
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
regs->pwmcount.set_write_cb(
|
2019-04-11 07:40:02 +02:00
|
|
|
[this](const scc::sc_register<uint32_t> ®, const uint32_t &data, sc_core::sc_time d) -> bool {
|
2018-11-08 13:31:28 +01:00
|
|
|
if (d.value()) wait(d);
|
|
|
|
reg.put(data);
|
|
|
|
update_counter();
|
|
|
|
current_cnt = data;
|
|
|
|
clk_remainder = 0.;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
regs->pwmcount.set_read_cb([this](const scc::sc_register<uint32_t> ®, uint32_t &data,
|
|
|
|
sc_core::sc_time d) -> bool {
|
|
|
|
auto offset = regs->r_pwmcfg.pwmenalways || regs->r_pwmcfg.pwmenoneshot ? static_cast<int>(get_pulses(d)) : 0;
|
|
|
|
data = current_cnt + offset;
|
|
|
|
regs->r_pwmcount.pwmcount = data;
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
regs->pwms.set_write_cb(
|
|
|
|
[this](scc::sc_register<uint32_t> ®, uint32_t data, sc_core::sc_time d) -> bool { return false; });
|
|
|
|
regs->pwms.set_read_cb([this](const scc::sc_register<uint32_t> ®, uint32_t &data, sc_core::sc_time d) -> bool {
|
|
|
|
auto offset = regs->r_pwmcfg.pwmenalways || regs->r_pwmcfg.pwmenoneshot ? static_cast<int>(get_pulses(d)) : 0;
|
|
|
|
auto cnt = current_cnt + offset;
|
|
|
|
data = (cnt >> regs->r_pwmcfg.pwmscale) & 0xffff;
|
|
|
|
regs->r_pwms.pwms = static_cast<uint16_t>(data);
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
regs->pwmcmp0.set_write_cb(
|
2019-04-11 07:40:02 +02:00
|
|
|
[this](const scc::sc_register<uint32_t> ®, const uint32_t &data, sc_core::sc_time d) -> bool {
|
2018-11-08 13:31:28 +01:00
|
|
|
reg.put(data);
|
|
|
|
update_counter();
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
regs->pwmcmp1.set_write_cb(
|
2019-04-11 07:40:02 +02:00
|
|
|
[this](const scc::sc_register<uint32_t> ®, const uint32_t &data, sc_core::sc_time d) -> bool {
|
2018-11-08 13:31:28 +01:00
|
|
|
reg.put(data);
|
|
|
|
update_counter();
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
regs->pwmcmp2.set_write_cb(
|
2019-04-11 07:40:02 +02:00
|
|
|
[this](const scc::sc_register<uint32_t> ®, const uint32_t &data, sc_core::sc_time d) -> bool {
|
2018-11-08 13:31:28 +01:00
|
|
|
reg.put(data);
|
|
|
|
update_counter();
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
regs->pwmcmp3.set_write_cb(
|
2019-04-11 07:40:02 +02:00
|
|
|
[this](const scc::sc_register<uint32_t> ®, const uint32_t &data, sc_core::sc_time d) -> bool {
|
2018-11-08 13:31:28 +01:00
|
|
|
reg.put(data);
|
|
|
|
update_counter();
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
|
|
|
|
SC_METHOD(clock_cb);
|
|
|
|
sensitive << clk_i;
|
|
|
|
SC_METHOD(reset_cb);
|
|
|
|
sensitive << rst_i;
|
|
|
|
SC_METHOD(update_counter);
|
|
|
|
sensitive << update_counter_evt;
|
|
|
|
dont_initialize();
|
|
|
|
}
|
|
|
|
|
|
|
|
void pwm::clock_cb() {
|
|
|
|
update_counter();
|
|
|
|
clk = clk_i.read();
|
|
|
|
}
|
|
|
|
|
|
|
|
pwm::~pwm() = default;
|
|
|
|
|
|
|
|
void pwm::reset_cb() {
|
|
|
|
if (rst_i.read()) {
|
|
|
|
regs->reset_start();
|
|
|
|
} else {
|
|
|
|
regs->reset_stop();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void pwm::update_counter() {
|
|
|
|
auto now = sc_time_stamp();
|
|
|
|
if (now == SC_ZERO_TIME) return;
|
|
|
|
update_counter_evt.cancel();
|
|
|
|
if (regs->r_pwmcfg.pwmenalways || regs->r_pwmcfg.pwmenoneshot) {
|
|
|
|
std::array<bool, 4> pwmcmp_new_ip{false, false, false, false};
|
|
|
|
auto dpulses = get_pulses(SC_ZERO_TIME);
|
|
|
|
auto pulses = static_cast<int>(dpulses);
|
|
|
|
clk_remainder += dpulses - pulses;
|
|
|
|
if (clk_remainder > 1) {
|
|
|
|
pulses++;
|
|
|
|
clk_remainder -= 1.0;
|
|
|
|
}
|
|
|
|
if (reset_cnt) {
|
|
|
|
current_cnt = 0;
|
|
|
|
reset_cnt = false;
|
|
|
|
} else if (last_enable)
|
|
|
|
current_cnt += pulses;
|
|
|
|
auto pwms = (current_cnt >> regs->r_pwmcfg.pwmscale) & 0xffff;
|
|
|
|
auto next_trigger_time =
|
|
|
|
(0xffff - pwms) * (1 << regs->r_pwmcfg.pwmscale) * clk; // next trigger based on wrap around
|
|
|
|
if (pwms == 0xffff) { // wrap around calculation
|
|
|
|
reset_cnt = true;
|
|
|
|
next_trigger_time = clk;
|
|
|
|
regs->r_pwmcfg.pwmenoneshot = 0;
|
|
|
|
}
|
|
|
|
auto pwms0 = (regs->r_pwmcfg.pwmcmp0center && (pwms & 0x8000) == 1) ? pwms ^ 0xffff : pwms;
|
|
|
|
if (pwms0 >= regs->r_pwmcmp0.pwmcmp0) {
|
|
|
|
pwmcmp_new_ip[0] = true;
|
|
|
|
regs->r_pwmcfg.pwmenoneshot = 0;
|
|
|
|
if (regs->r_pwmcfg.pwmzerocmp) {
|
|
|
|
reset_cnt = true;
|
|
|
|
next_trigger_time = clk;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pwmcmp_new_ip[0] = false;
|
|
|
|
// TODO: add correct calculation for regs->r_pwmcfg.pwmcmpXcenter==1
|
|
|
|
auto nt = (regs->r_pwmcmp0.pwmcmp0 - pwms0) * (1 << regs->r_pwmcfg.pwmscale) * clk;
|
|
|
|
next_trigger_time = nt < next_trigger_time ? nt : next_trigger_time;
|
|
|
|
}
|
|
|
|
auto pwms1 = (regs->r_pwmcfg.pwmcmp0center && (pwms & 0x8000) == 1) ? pwms ^ 0xffff : pwms;
|
|
|
|
if (pwms1 >= regs->r_pwmcmp1.pwmcmp0) {
|
|
|
|
pwmcmp_new_ip[1] = true;
|
|
|
|
} else {
|
|
|
|
pwmcmp_new_ip[1] = false;
|
|
|
|
// TODO: add correct calculation for regs->r_pwmcfg.pwmcmpXcenter==1
|
|
|
|
auto nt = (regs->r_pwmcmp0.pwmcmp0 - pwms0) * (1 << regs->r_pwmcfg.pwmscale) * clk;
|
|
|
|
next_trigger_time = nt < next_trigger_time ? nt : next_trigger_time;
|
|
|
|
}
|
|
|
|
auto pwms2 = (regs->r_pwmcfg.pwmcmp0center && (pwms & 0x8000) == 1) ? pwms ^ 0xffff : pwms;
|
|
|
|
if (pwms2 >= regs->r_pwmcmp2.pwmcmp0) {
|
|
|
|
pwmcmp_new_ip[2] = true;
|
|
|
|
} else {
|
|
|
|
pwmcmp_new_ip[2] = false;
|
|
|
|
// TODO: add correct calculation for regs->r_pwmcfg.pwmcmpXcenter==1
|
|
|
|
auto nt = (regs->r_pwmcmp0.pwmcmp0 - pwms0) * regs->r_pwmcfg.pwmscale * clk;
|
|
|
|
next_trigger_time = nt < next_trigger_time ? nt : next_trigger_time;
|
|
|
|
}
|
|
|
|
auto pwms3 = (regs->r_pwmcfg.pwmcmp0center && (pwms & 0x8000) == 1) ? pwms ^ 0xffff : pwms;
|
|
|
|
if (pwms3 >= regs->r_pwmcmp3.pwmcmp0) {
|
|
|
|
pwmcmp_new_ip[3] = true;
|
|
|
|
} else {
|
|
|
|
pwmcmp_new_ip[3] = false;
|
|
|
|
// TODO: add correct calculation for regs->r_pwmcfg.pwmcmpXcenter==1
|
|
|
|
auto nt = (regs->r_pwmcmp0.pwmcmp0 - pwms0) * (1 << regs->r_pwmcfg.pwmscale) * clk;
|
|
|
|
next_trigger_time = nt < next_trigger_time ? nt : next_trigger_time;
|
|
|
|
}
|
|
|
|
for (size_t i = 0; i < 4; ++i) {
|
|
|
|
// write gpio bits depending of gang bit
|
|
|
|
if (regs->r_pwmcfg & (1 < (24 + i)))
|
|
|
|
write_cmpgpio(i, pwmcmp_new_ip[i] && !pwmcmp_new_ip[(i + 1) % 4]);
|
|
|
|
else
|
|
|
|
write_cmpgpio(i, pwmcmp_new_ip[i]);
|
|
|
|
// detect rising edge and set ip bit if found
|
|
|
|
if (!pwmcmp_ip[i] && pwmcmp_new_ip[i]) regs->r_pwmcfg |= 1 << (28 + i);
|
|
|
|
pwmcmp_ip[i] = pwmcmp_new_ip[i];
|
|
|
|
}
|
|
|
|
last_enable = true;
|
|
|
|
update_counter_evt.notify(next_trigger_time);
|
|
|
|
} else
|
|
|
|
last_enable = false;
|
|
|
|
cmpip_o[0].write(regs->r_pwmcfg.pwmcmp0ip != 0);
|
|
|
|
cmpip_o[1].write(regs->r_pwmcfg.pwmcmp1ip != 0);
|
|
|
|
cmpip_o[2].write(regs->r_pwmcfg.pwmcmp2ip != 0);
|
|
|
|
cmpip_o[3].write(regs->r_pwmcfg.pwmcmp3ip != 0);
|
|
|
|
last_cnt_update = now;
|
|
|
|
last_clk = clk;
|
|
|
|
}
|
|
|
|
|
|
|
|
void pwm::write_cmpgpio(size_t index, bool val) {
|
|
|
|
if (cmpgpio_o[index].get_interface()) {
|
|
|
|
tlm::tlm_phase phase(tlm::BEGIN_REQ);
|
|
|
|
tlm::tlm_signal_gp<> gp;
|
|
|
|
sc_core::sc_time delay(SC_ZERO_TIME);
|
|
|
|
gp.set_value(val);
|
|
|
|
cmpgpio_o[index]->nb_transport_fw(gp, phase, delay);
|
|
|
|
}
|
|
|
|
}
|