sim.blackboxes: add serial blackbox, with a serial_pty driver.
authorJean-François Nguyen <jf@lambdaconcept.com>
Fri, 29 Oct 2021 12:10:08 +0000 (14:10 +0200)
committerJean-François Nguyen <jf@lambdaconcept.com>
Fri, 29 Oct 2021 12:12:39 +0000 (14:12 +0200)
lambdasoc/sim/blackboxes/serial/__init__.py [new file with mode: 0644]
lambdasoc/sim/blackboxes/serial/drivers/__init__.py [new file with mode: 0644]
lambdasoc/sim/blackboxes/serial/drivers/serial_pty/__init__.py [new file with mode: 0644]
lambdasoc/sim/blackboxes/serial/drivers/serial_pty/serial_pty.cc [new file with mode: 0644]
lambdasoc/sim/blackboxes/serial/wrapper.py [new file with mode: 0644]

diff --git a/lambdasoc/sim/blackboxes/serial/__init__.py b/lambdasoc/sim/blackboxes/serial/__init__.py
new file mode 100644 (file)
index 0000000..88cd447
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..4ea8dcb
--- /dev/null
@@ -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 (file)
index 0000000..6156152
--- /dev/null
@@ -0,0 +1,221 @@
+#include <cassert>
+#include <fcntl.h>
+#include <iostream>
+#include <map>
+#include <memory>
+#include <poll.h>
+#include <stdexcept>
+#include <string>
+#include <termios.h>
+#include <unistd.h>
+#include <util/log_fmt.h>
+#include <vector>
+
+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<const std::string, std::weak_ptr<serial_pty>> 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<serial_pty> get(const std::string &id) {
+        std::shared_ptr<serial_pty> desc;
+        if (!serial_pty_map.count(id)) {
+            desc = std::make_shared<serial_pty>(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</*DATA_BITS=*/8> {
+    std::shared_ptr<serial_pty> desc;
+    std::vector<unsigned char> buffer;
+
+    serial_pty_rx(const std::shared_ptr<serial_pty> &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<bool>() & p_rdy.curr.get<bool>()) {
+                assert(!buffer.empty());
+                buffer.erase(buffer.begin());
+                p_rdy.next.set<bool>(false);
+            }
+            if (desc->pty.readable()) {
+                buffer.insert(buffer.end(), desc->pty.read_char());
+            }
+            if (!buffer.empty()) {
+                p_rdy.next.set<bool>(true);
+                p_data.next.set<unsigned char>(buffer.front());
+            }
+        }
+        return bb_p_serial__rx</*DATA_BITS=*/8>::eval();
+    }
+};
+
+template<>
+std::unique_ptr<bb_p_serial__rx</*DATA_BITS=*/8>>
+bb_p_serial__rx</*DATA_BITS=*/8>::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<serial_pty> desc = serial_pty::get(id);
+    std::cout << "Assigning '" << name << "' to " << ptsname(desc->pty.fd) << "\n";
+
+    return std::make_unique<serial_pty_rx>(desc);
+}
+
+// Transmitter
+
+struct serial_pty_tx : public bb_p_serial__tx</*DATA_BITS=*/8> {
+    const std::shared_ptr<serial_pty> desc;
+
+    serial_pty_tx(const std::shared_ptr<serial_pty> &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<bool>() & p_rdy.curr.get<bool>()) {
+                desc->pty.write_char(p_data.get<unsigned char>());
+            }
+            p_rdy.next.set<bool>(desc->pty.writable());
+        }
+        return bb_p_serial__tx</*DATA_BITS=*/8>::eval();
+    }
+};
+
+template<>
+std::unique_ptr<bb_p_serial__tx</*DATA_BITS=*/8>>
+bb_p_serial__tx</*DATA_BITS=*/8>::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<serial_pty> desc = serial_pty::get(id);
+    std::cout << "Assigning '" << name << "' to " << ptsname(desc->pty.fd) << "\n";
+
+    return std::make_unique<serial_pty_tx>(desc);
+}
+
+} // namespace cxxrtl_design
diff --git a/lambdasoc/sim/blackboxes/serial/wrapper.py b/lambdasoc/sim/blackboxes/serial/wrapper.py
new file mode 100644 (file)
index 0000000..c71939c
--- /dev/null
@@ -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