From: Jean-François Nguyen Date: Fri, 29 Oct 2021 12:10:08 +0000 (+0200) Subject: sim.blackboxes: add serial blackbox, with a serial_pty driver. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=10c3da8118d045de61c0db4acff1fb1e1631fecc;p=lambdasoc.git sim.blackboxes: add serial blackbox, with a serial_pty driver. --- diff --git a/lambdasoc/sim/blackboxes/serial/__init__.py b/lambdasoc/sim/blackboxes/serial/__init__.py new file mode 100644 index 0000000..88cd447 --- /dev/null +++ b/lambdasoc/sim/blackboxes/serial/__init__.py @@ -0,0 +1 @@ +from .wrapper import * diff --git a/lambdasoc/sim/blackboxes/serial/drivers/__init__.py b/lambdasoc/sim/blackboxes/serial/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lambdasoc/sim/blackboxes/serial/drivers/serial_pty/__init__.py b/lambdasoc/sim/blackboxes/serial/drivers/serial_pty/__init__.py new file mode 100644 index 0000000..4ea8dcb --- /dev/null +++ b/lambdasoc/sim/blackboxes/serial/drivers/serial_pty/__init__.py @@ -0,0 +1,3 @@ +cxxrtl_src_files = [ + (__package__, (), "serial_pty.cc"), +] diff --git a/lambdasoc/sim/blackboxes/serial/drivers/serial_pty/serial_pty.cc b/lambdasoc/sim/blackboxes/serial/drivers/serial_pty/serial_pty.cc new file mode 100644 index 0000000..6156152 --- /dev/null +++ b/lambdasoc/sim/blackboxes/serial/drivers/serial_pty/serial_pty.cc @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct pty_file { + const int fd; + + pty_file() + : fd(posix_openpt(O_RDWR | O_NOCTTY)) { + if (fd < 0) { + throw std::runtime_error(fmt_errno("posix_openpt")); + } + } + + ~pty_file() { + close(fd); + } + + void prepare() const { + if (grantpt(fd)) { + throw std::runtime_error(fmt_errno("grantpt")); + } + if (unlockpt(fd)) { + throw std::runtime_error(fmt_errno("unlockpt")); + } + struct termios raw; + if (tcgetattr(fd, &raw)) { + throw std::runtime_error(fmt_errno("tcgetattr")); + } + raw.c_cflag = (raw.c_cflag & ~CSIZE) | CS8; + raw.c_lflag &= ~(ECHO | ICANON); + if (tcsetattr(fd, TCSANOW, &raw)) { + throw std::runtime_error(fmt_errno("tcsetattr")); + } + } + + bool readable() const { + pollfd pfd = {fd, POLLIN, 0}; + poll(&pfd, /*nfds=*/1, /*timeout=*/0); + return (pfd.revents & POLLIN); + } + + bool writable() const { + pollfd pfd = {fd, POLLOUT, 0}; + poll(&pfd, /*nfds=*/1, /*timeout=*/0); + return (pfd.revents & POLLOUT); + } + + unsigned char read_char() const { + unsigned char c; + ssize_t nread = read(fd, &c, /*count=*/1); + if (nread != 1) { + throw std::runtime_error(fmt_errno("read")); + } + return c; + } + + void write_char(unsigned char c) const { + ssize_t nwrite = write(fd, &c, /*count=*/1); + if (nwrite != 1) { + throw std::runtime_error(fmt_errno("write")); + } + } +}; + +struct serial_pty; +static std::map> serial_pty_map; + +struct serial_pty { +protected: + bool _has_rx; + bool _has_tx; + +public: + const std::string id; + const pty_file pty; + + serial_pty(const std::string &id) + : _has_rx(false) + , _has_tx(false) + , id(id) + , pty() { + pty.prepare(); + } + + serial_pty() = delete; + serial_pty(const serial_pty &) = delete; + serial_pty &operator=(const serial_pty &) = delete; + + ~serial_pty() { + if (serial_pty_map.count(id)) { + serial_pty_map.erase(id); + } + } + + static std::shared_ptr get(const std::string &id) { + std::shared_ptr desc; + if (!serial_pty_map.count(id)) { + desc = std::make_shared(id); + serial_pty_map[id] = desc; + } else { + desc = serial_pty_map[id].lock(); + assert(desc); + } + return desc; + } + + void set_rx() { + _has_rx = true; + } + void set_tx() { + _has_tx = true; + } + + bool has_rx() const { + return _has_rx; + } + bool has_tx() const { + return _has_tx; + } +}; + +namespace cxxrtl_design { + +// Receiver + +struct serial_pty_rx : public bb_p_serial__rx { + std::shared_ptr desc; + std::vector buffer; + + serial_pty_rx(const std::shared_ptr &desc) + : desc(desc) { + if (desc->has_rx()) { + throw std::invalid_argument(fmt_msg("RX port collision")); + } + desc->set_rx(); + } + + void reset() override {} + + bool eval() override { + if (posedge_p_clk()) { + if (p_ack.get() & p_rdy.curr.get()) { + assert(!buffer.empty()); + buffer.erase(buffer.begin()); + p_rdy.next.set(false); + } + if (desc->pty.readable()) { + buffer.insert(buffer.end(), desc->pty.read_char()); + } + if (!buffer.empty()) { + p_rdy.next.set(true); + p_data.next.set(buffer.front()); + } + } + return bb_p_serial__rx::eval(); + } +}; + +template<> +std::unique_ptr> +bb_p_serial__rx::create(std::string name, cxxrtl::metadata_map parameters, + cxxrtl::metadata_map attributes) { + assert(parameters.count("ID")); + const std::string &id = parameters["ID"].as_string(); + + std::shared_ptr desc = serial_pty::get(id); + std::cout << "Assigning '" << name << "' to " << ptsname(desc->pty.fd) << "\n"; + + return std::make_unique(desc); +} + +// Transmitter + +struct serial_pty_tx : public bb_p_serial__tx { + const std::shared_ptr desc; + + serial_pty_tx(const std::shared_ptr &desc) + : desc(desc) { + if (desc->has_tx()) { + throw std::invalid_argument(fmt_msg("TX port collision")); + } + desc->set_tx(); + } + + void reset() override {} + + bool eval() override { + if (posedge_p_clk()) { + if (p_ack.get() & p_rdy.curr.get()) { + desc->pty.write_char(p_data.get()); + } + p_rdy.next.set(desc->pty.writable()); + } + return bb_p_serial__tx::eval(); + } +}; + +template<> +std::unique_ptr> +bb_p_serial__tx::create(std::string name, cxxrtl::metadata_map parameters, + cxxrtl::metadata_map attributes) { + assert(parameters.count("ID")); + const std::string &id = parameters["ID"].as_string(); + + std::shared_ptr desc = serial_pty::get(id); + std::cout << "Assigning '" << name << "' to " << ptsname(desc->pty.fd) << "\n"; + + return std::make_unique(desc); +} + +} // namespace cxxrtl_design diff --git a/lambdasoc/sim/blackboxes/serial/wrapper.py b/lambdasoc/sim/blackboxes/serial/wrapper.py new file mode 100644 index 0000000..c71939c --- /dev/null +++ b/lambdasoc/sim/blackboxes/serial/wrapper.py @@ -0,0 +1,89 @@ +from nmigen import * +from nmigen.utils import bits_for + + +__all__ = ["AsyncSerialRX_Blackbox", "AsyncSerialTX_Blackbox", "AsyncSerial_Blackbox"] + + +class AsyncSerialRX_Blackbox(Elaboratable): + def __init__(self, *, divisor, divisor_bits=None, data_bits=8, parity="none", parent=None): + if parent is not None and not isinstance(parent, AsyncSerial_Blackbox): + raise TypeError("Parent must be an instance of AsyncSerial_Blackbox, not {!r}" + .format(parent)) + self.parent = parent + + self.divisor = Signal(divisor_bits or bits_for(divisor)) + + self.data = Signal(data_bits) + self.err = Record([ + ("overflow", 1), + ("frame", 1), + ("parity", 1), + ]) + self.rdy = Signal() + self.ack = Signal() + + def elaborate(self, platform): + return Instance("serial_rx", + p_ID = hex(id(self.parent) if self.parent else id(self)), + p_DATA_BITS = len(self.data), + i_clk = ClockSignal("sync"), + o_data = self.data, + o_err_overflow = self.err.overflow, + o_err_frame = self.err.frame, + o_err_parity = self.err.parity, + o_rdy = self.rdy, + i_ack = self.ack, + ) + + +class AsyncSerialTX_Blackbox(Elaboratable): + def __init__(self, *, divisor, divisor_bits=None, data_bits=8, parity="none", parent=None): + if parent is not None and not isinstance(parent, AsyncSerial_Blackbox): + raise TypeError("Parent must be an instance of AsyncSerial_Blackbox, not {!r}" + .format(parent)) + self._parent = parent + + self.divisor = Signal(divisor_bits or bits_for(divisor)) + + self.data = Signal(data_bits) + self.rdy = Signal() + self.ack = Signal() + + def elaborate(self, platform): + return Instance("serial_tx", + p_ID = hex(id(self._parent) if self._parent else id(self)), + p_DATA_BITS = len(self.data), + i_clk = ClockSignal("sync"), + i_data = self.data, + o_rdy = self.rdy, + i_ack = self.ack, + ) + + +class AsyncSerial_Blackbox(Elaboratable): + def __init__(self, *, divisor, divisor_bits=None, **kwargs): + self.divisor = Signal(divisor_bits or bits_for(divisor), reset=divisor) + + self.rx = AsyncSerialRX_Blackbox( + divisor = divisor, + divisor_bits = divisor_bits, + parent = self, + **kwargs + ) + self.tx = AsyncSerialTX_Blackbox( + divisor = divisor, + divisor_bits = divisor_bits, + parent = self, + **kwargs + ) + + def elaborate(self, platform): + m = Module() + m.submodules.rx = self.rx + m.submodules.tx = self.tx + m.d.comb += [ + self.rx.divisor.eq(self.divisor), + self.tx.divisor.eq(self.divisor), + ] + return m