From: Florent Kermarrec Date: Thu, 22 Jan 2015 20:40:07 +0000 (+0100) Subject: start refactoring and change name to LiteScope X-Git-Tag: 24jan2021_ls180~2575^2~45 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=f35f93a7c5601af9daec89a0de321a432110a729;p=litex.git start refactoring and change name to LiteScope --- diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..95ac4a6a --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ +Unless otherwise noted, LiteScope is copyright (C) 2015 Florent Kermarrec. + +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. + +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 OWNER 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. + + +Other authors retain ownership of their contributions. If a submission can +reasonably be considered independently copyrightable, it's yours and we +encourage you to claim it with appropriate copyright notices. This submission +then falls under the "otherwise noted" category. All submissions are strongly +encouraged to use the two-clause BSD license reproduced above. diff --git a/README b/README index a2239f92..fd682777 100644 --- a/README +++ b/README @@ -1,48 +1,130 @@ - _____ _ ____ _ _ _ _ - | __|___ |_|___ _ _ | \|_|___|_| |_ ___| | - | __| | | | . | | | | | | | . | | _| .'| | - |_____|_|_|_| |___|_ | |____/|_|_ |_|_| |__,|_| - |___| |___| |___| - - Copyright 2012-2014 / Florent Kermarrec / florent@enjoy-digital.fr - - Miscope --------------------------------------------------------------------------------- - -[> Miscope ------------- - -Miscope is a small logic analyzer to embed in an FPGA. - -While free vendor toolchains are generally used by beginners or for prototyping -(situations where having a logic analyzer in the design is generally helpful) -free toolchains are always provided without the proprietary logic analyzer -solution... :( - -Baseid on Migen, Miscope aims to provide a free, portable and flexible + __ _ __ ____ + / / (_) /____ / __/______ ___ ___ + / /__/ / __/ -_)\ \/ __/ _ \/ _ \/ -_) + /____/_/\__/\__/___/\__/\___/ .__/\__/ + /_/ + Copyright 2012-2015 / EnjoyDigital + florent@enjoy-digital.fr + + A small footprint and configurable embedded FPGA + logic analyzer core by EnjoyDigital + +[> Intro +--------- +LiteScope small footprint and configurable embedded logic analyzer that you +can use in your FPGA and aims to provide a a free, portable and flexible alternatve to vendor's solutions! -[> Specification: +LiteScope is part of LiteX libraries whose aims is to lower entry level of complex +FPGA IP cores by providing simple, elegant and efficient implementations of +components used in today's SoC such as Ethernet, SATA, PCIe, SDRAM Controller... + +The core uses simple and specific streaming buses and will provides in the future +adapters to use standardized AXI or Avalon-ST streaming buses. + +Since Python is used to describe the HDL, the core is highly and easily +configurable. + +LiteScope uses technologies developed in partnership with M-Labs Ltd: + - Migen enables generating HDL with Python in an efficient way. + - MiSoC provides the basic blocks to build a powerful and small footprint SoC. + +LiteScope can be used as a Migen/MiSoC library (by simply installing it +with the provided setup.py) or can be integrated with your standard design flow +by generating the verilog rtl that you will use as a standard core. + +LiteScope produces "vcd" files that can be read in your regular waveforms viewer. + +Since LiteScope also provides an UART <--> Wishbone brige you only need 2 external +Rx/Tx pins to be ready to debug or control all your Wishbone peripherals! + +[> Features +----------- +- IO peek and poke with LiteScopeIO. +- Logic analyser with LiteScopeLA: + - Various triggering modules: Term, Range, Edge (add yours! :) + - Run Length Encoder to "compress" data and increase recording depth + - Data storage in block rams + + +[> Possibles improvements +------------------------- +- add standardized interfaces (AXI, Avalon-ST) +- add storage in DRAM +- add storage in HDD with LiteSATA core (https://github.com/enjoy-digital/litesata) +- add Ethernet Wishbone bridge +- add PCIe Wishbone bridge with LitePCIe (to be released soon!) +- ... See below Support and Consulting :) + +If you want to support these features, please contact us at florent [AT] +enjoy-digital.fr. You can also contact our partner on the public mailing list +devel [AT] lists.m-labs.hk. + + +[> Getting started +------------------ +1. Install Python3 and Xilinx's Vivado software + +2. Obtain Migen and install it: + git clone https://github.com/m-labs/migen + cd migen + python3 setup.py install + cd .. + +3. Obtain Miscope and install it: + git clone https://github.com/m-labs/miscope + cd miscope + python3 setup.py install + cd .. + +4. Obtain MiSoC: + git clone https://github.com/m-labs/misoc --recursive + XXX add setup.py to MiSoC for external use of misoclib? + +5. Obtain LiteScope + git clone https://github.com/enjoy-digital/litescope + +6. Build and load test design (only for KC705 for now): + python3 make.py -s [platform] all + Supported platform are the supported platform of Mibuild: + de0nano, m1, mixxeo, kc705, zedboard... + +7. Test design: + go to ./test directory and run: + python3 test_io.py + python3 test_la.py + +[> Simulations: + XXX convert simulations + +[> Tests : + XXX convert tests -Miscope provides Migen cores to embed in the design and Python drivers to control -the logic analyzer from the Host. Miscope automatically interconnects all cores -to a CSR bus. When using Python on the Host, no needs to worry about cores register -mapping, importing miscope project gives you direct access to all the cores! +[> License +----------- +LiteScope is released under the very permissive two-clause BSD license. Under the +terms of this license, you are authorized to use LiteScope for closed-source +proprietary designs. +Even though we do not require you to do so, those things are awesome, so please +do them if possible: + - tell us that you are using LiteScope + - cite LiteScope in publications related to research it has helped + - send us feedback and suggestions for improvements + - send us bug reports when something goes wrong + - send us the modifications and improvements you have done to LiteScope. -Miscope produces .vcd output files to be analyzed in your favorite waveform viewer. +[> Support and Consulting +-------------------------- +We love open-source hardware and like sharing our designs with others. -Since Miscope also provides an Uart2Wishbone bridge, you only need 2 external Rx/Tx -pins to be ready to debug! +LiteScope is developed and maintained by EnjoyDigital. -[> Status: -MiIo & Mila working on board with standard term. -RLE working on board. -RangeDetector and EdgeDector terms not tested. +If you would like to know more about LiteScope or if you are already a happy user +and would like to extend it for your needs, EnjoyDigital can provide standard +commercial support as well as consulting services. -[> Examples: -Have a look at http://github.com/Florent-Kermarrec/misoc-de0nano -test_miio.py : Led & Switch Test controlled by Python Host. -test_mila.py : Logic Analyzer controlled by Python Host. +So feel free to contact us, we'd love to work with you! (and eventually shorten +the list of the possible improvements :) [> Contact -E-mail: florent@enjoy-digital.fr +E-mail: florent [AT] enjoy-digital.fr \ No newline at end of file diff --git a/litesata-version.txt b/litesata-version.txt new file mode 100644 index 00000000..eba33402 --- /dev/null +++ b/litesata-version.txt @@ -0,0 +1,2 @@ +0.9.0 + diff --git a/litescope/__init__.py b/litescope/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/litescope/bridge/uart2wb.py b/litescope/bridge/uart2wb.py new file mode 100644 index 00000000..697843b3 --- /dev/null +++ b/litescope/bridge/uart2wb.py @@ -0,0 +1,197 @@ +from migen.fhdl.std import * +from migen.genlib.record import * +from migen.genlib.fsm import FSM, NextState +from migen.genlib.misc import chooser +from migen.bank.description import * +from migen.bus import wishbone + +from misoclib.uart import UARTRX, UARTTX + +from litescope.common import * + +class UART(Module, AutoCSR): + def __init__(self, pads, clk_freq, baud=115200): + self._tuning_word = CSRStorage(32, reset=int((baud/clk_freq)*2**32)) + tuning_word = self._tuning_word.storage + + ### + + self.rx = UARTRX(pads, tuning_word) + self.tx = UARTTX(pads, tuning_word) + self.submodules += self.rx, self.tx + +class UARTPads: + def __init__(self): + self.rx = Signal() + self.tx = Signal() + +class UARTMux(Module): + def __init__(self, pads): + self.sel = Signal(max=2) + self.shared_pads = UARTPads() + self.bridge_pads = UARTPads() + + ### + # Route rx pad: + # when sel==0, route it to shared rx and bridge rx + # when sel==1, route it only to bridge rx + self.comb += \ + If(self.sel==0, + self.shared_pads.rx.eq(pads.rx), + self.bridge_pads.rx.eq(pads.rx) + ).Else( + self.bridge_pads.rx.eq(pads.rx) + ) + + # Route tx: + # when sel==0, route shared tx to pads tx + # when sel==1, route bridge tx to pads tx + self.comb += \ + If(self.sel==0, + pads.tx.eq(self.shared_pads.tx) + ).Else( + pads.tx.eq(self.bridge_pads.tx) + ) + +class LiteScopeUART2WB(Module, AutoCSR): + cmds = { + "write" : 0x01, + "read" : 0x02 + } + def __init__(self, pads, clk_freq, baud=115200, share_uart=False): + self.wishbone = wishbone.Interface() + if share_uart: + self._sel = CSRStorage() + ### + if share_uart: + uart_mux = UARTMux(pads) + uart = UART(uart_mux.bridge_pads, clk_freq, baud) + self.submodules += uart_mux, uart + self.shared_pads = uart_mux.shared_pads + self.comb += uart_mux.sel.eq(self._sel.storage) + else: + uart = UART(pads, clk_freq, baud) + self.submodules += uart + + byte_counter = Counter(bits_sign=3) + word_counter = Counter(bits_sign=8) + self.submodules += byte_counter, word_counter + + cmd = Signal(8) + cmd_ce = Signal() + + length = Signal(8) + length_ce = Signal() + + address = Signal(32) + address_ce = Signal() + + data = Signal(32) + rx_data_ce = Signal() + tx_data_ce = Signal() + + self.sync += [ + If(cmd_ce, cmd.eq(uart.rx.source.d)), + If(length_ce, length.eq(uart.rx.source.d)), + If(address_ce, address.eq(Cat(uart.rx.source.d, address[0:24]))), + If(rx_data_ce, + data.eq(Cat(uart.rx.source.d, data[0:24])) + ).Elif(tx_data_ce, + data.eq(self.wishbone.dat_r) + ) + ] + + ### + fsm = InsertReset(FSM(reset_state="IDLE")) + timeout = Timeout(clk_freq//10) + self.submodules += fsm, timeout + self.comb += [ + timeout.ce.eq(1), + fsm.reset.eq(timeout.reached) + ] + fsm.act("IDLE", + timeout.reset.eq(1), + If(uart.rx.source.stb, + cmd_ce.eq(1), + If( (uart.rx.source.d == self.cmds["write"]) | + (uart.rx.source.d == self.cmds["read"]), + NextState("RECEIVE_LENGTH") + ), + byte_counter.reset.eq(1), + word_counter.reset.eq(1) + ) + ) + fsm.act("RECEIVE_LENGTH", + If(uart.rx.source.stb, + length_ce.eq(1), + NextState("RECEIVE_ADDRESS") + ) + ) + fsm.act("RECEIVE_ADDRESS", + If(uart.rx.source.stb, + address_ce.eq(1), + byte_counter.ce.eq(1), + If(byte_counter.value == 3, + If(cmd == self.cmds["write"], + NextState("RECEIVE_DATA") + ).Elif(cmd == self.cmds["read"], + NextState("READ_DATA") + ), + byte_counter.reset.eq(1), + ) + ) + ) + fsm.act("RECEIVE_DATA", + If(uart.rx.source.stb, + rx_data_ce.eq(1), + byte_counter.ce.eq(1), + If(byte_counter.value == 3, + NextState("WRITE_DATA"), + byte_counter.reset.eq(1) + ) + ) + ) + self.comb += [ + self.wishbone.adr.eq(address + word_counter.value), + self.wishbone.dat_w.eq(data), + self.wishbone.sel.eq(2**flen(self.wishbone.sel)-1) + ] + fsm.act("WRITE_DATA", + self.wishbone.stb.eq(1), + self.wishbone.we.eq(1), + self.wishbone.cyc.eq(1), + If(self.wishbone.ack, + word_counter.ce.eq(1), + If(word_counter.value == (length-1), + NextState("IDLE") + ).Else( + NextState("RECEIVE_DATA") + ) + ) + ) + fsm.act("READ_DATA", + self.wishbone.stb.eq(1), + self.wishbone.we.eq(0), + self.wishbone.cyc.eq(1), + If(self.wishbone.ack, + tx_data_ce.eq(1), + NextState("SEND_DATA") + ) + ) + self.comb += \ + chooser(data, byte_counter.value, uart.tx.sink.d, n=4, reverse=True) + fsm.act("SEND_DATA", + uart.tx.sink.stb.eq(1), + If(uart.tx.sink.ack, + byte_counter.ce.eq(1), + If(byte_counter.value == 3, + word_counter.ce.eq(1), + If(word_counter.value == (length-1), + NextState("IDLE") + ).Else( + NextState("READ_DATA"), + byte_counter.reset.eq(1) + ) + ) + ) + ) diff --git a/litescope/common.py b/litescope/common.py new file mode 100644 index 00000000..b6bc372e --- /dev/null +++ b/litescope/common.py @@ -0,0 +1,36 @@ +from migen.genlib.record import * + +def dat_layout(dw): + return [ + ("stb", 1, DIR_M_TO_S), + ("dat", dw, DIR_M_TO_S) + ] + +def hit_layout(): + return [ + ("stb", 1, DIR_M_TO_S), + ("hit", 1, DIR_M_TO_S) + ] + +@DecorateModule(InsertReset) +@DecorateModule(InsertCE) +class Counter(Module): + def __init__(self, signal=None, **kwargs): + if signal is None: + self.value = Signal(**kwargs) + else: + self.value = signal + self.width = flen(self.value) + self.sync += self.value.eq(self.value+1) + +@DecorateModule(InsertReset) +@DecorateModule(InsertCE) +class Timeout(Module): + def __init__(self, length): + self.reached = Signal() + ### + value = Signal(max=length) + self.sync += value.eq(value+1) + self.comb += [ + self.reached.eq(value == length) + ] diff --git a/litescope/core/storage.py b/litescope/core/storage.py new file mode 100644 index 00000000..cbd14c93 --- /dev/null +++ b/litescope/core/storage.py @@ -0,0 +1,114 @@ +from migen.fhdl.std import * +from migen.bank.description import * +from migen.genlib.fifo import SyncFIFOBuffered as SyncFIFO +from migen.genlib.fsm import FSM, NextState +from migen.genlib.record import * + +from litescope.common import * + +class LiteScopeRunLengthEncoder(Module, AutoCSR): + def __init__(self, width, length=1024): + self.width = width + self.length = length + + self.sink = Record(dat_layout(width)) + self.source = Record(dat_layout(width)) + + self._enable = CSRStorage() + + ### + + enable = self._enable.storage + + sink_d = Record(dat_layout(width)) + self.sync += If(self.sink.stb, sink_d.eq(self.sink)) + + cnt = Signal(max=length) + cnt_inc = Signal() + cnt_reset = Signal() + cnt_max = Signal() + + self.sync += \ + If(cnt_reset, + cnt.eq(1), + ).Elif(cnt_inc, + cnt.eq(cnt+1) + ) + self.comb += cnt_max.eq(cnt == length) + + change = Signal() + self.comb += change.eq(self.sink.stb & (self.sink.dat != sink_d.dat)) + + fsm = FSM(reset_state="BYPASS") + self.submodules += fsm + + fsm.act("BYPASS", + sink_d.connect(self.source), + cnt_reset.eq(1), + If(enable & ~change & self.sink.stb, NextState("COUNT")) + ) + + fsm.act("COUNT", + cnt_inc.eq(self.sink.stb), + If(change | cnt_max | ~enable, + self.source.stb.eq(1), + self.source.dat[width-1].eq(1), # Set RLE bit + self.source.dat[:flen(cnt)].eq(cnt), + NextState("BYPASS") + ) + ), + +class LiteScopeRecorder(Module, AutoCSR): + def __init__(self, width, depth): + self.width = width + + self.trig_sink = Record(hit_layout()) + self.dat_sink = Record(dat_layout(width)) + + self._trigger = CSR() + self._length = CSRStorage(bits_for(depth)) + self._offset = CSRStorage(bits_for(depth)) + self._done = CSRStatus() + + self._read_en = CSR() + self._read_empty = CSRStatus() + self._read_dat = CSRStatus(width) + + ### + + fifo = InsertReset(SyncFIFO(width, depth)) + self.submodules += fifo + + fsm = FSM(reset_state="IDLE") + self.submodules += fsm + + + self.comb += [ + self._read_empty.status.eq(~fifo.readable), + self._read_dat.status.eq(fifo.dout), + ] + + fsm.act("IDLE", + If(self._trigger.re & self._trigger.r, + NextState("PRE_HIT_RECORDING"), + fifo.reset.eq(1), + ), + fifo.re.eq(self._read_en.re & self._read_en.r), + self._done.status.eq(1) + ) + + fsm.act("PRE_HIT_RECORDING", + fifo.we.eq(self.dat_sink.stb), + fifo.din.eq(self.dat_sink.dat), + + fifo.re.eq(fifo.level >= self._offset.storage), + + If(self.trig_sink.stb & self.trig_sink.hit, NextState("POST_HIT_RECORDING")) + ) + + fsm.act("POST_HIT_RECORDING", + fifo.we.eq(self.dat_sink.stb), + fifo.din.eq(self.dat_sink.dat), + + If(~fifo.writable | (fifo.level >= self._length.storage), NextState("IDLE")) + ) diff --git a/litescope/core/trigger.py b/litescope/core/trigger.py new file mode 100644 index 00000000..f9b792be --- /dev/null +++ b/litescope/core/trigger.py @@ -0,0 +1,140 @@ +from migen.fhdl.std import * +from migen.fhdl.specials import Memory +from migen.bank.description import * +from migen.genlib.record import * + +from litescope.common import * + +class LiteScopeTerm(Module, AutoCSR): + def __init__(self, width): + self.width = width + + self.sink = Record(dat_layout(width)) + self.source = Record(hit_layout()) + + self._trig = CSRStorage(width) + self._mask = CSRStorage(width) + + ### + + trig = self._trig.storage + mask = self._mask.storage + dat = self.sink.dat + hit = self.source.hit + + self.comb += [ + hit.eq((dat & mask) == trig), + self.source.stb.eq(self.sink.stb) + ] + +class LiteScopeRangeDetector(Module, AutoCSR): + def __init__(self, width): + self.width = width + + self.sink = Record(dat_layout(width)) + self.source = Record(hit_layout()) + + self._low = CSRStorage(width) + self._high = CSRStorage(width) + + ### + + low = self._low.storage + high = self._high.storage + dat = self.sink.dat + hit = self.source.hit + + self.comb += [ + hit.eq((dat >= low) & (dat <= high)), + self.source.stb.eq(self.sink.stb) + ] + +class LiteScopeEdgeDetector(Module, AutoCSR): + def __init__(self, width): + self.width = width + + self.sink = Record(dat_layout(width)) + self.source = Record(hit_layout()) + + self._rising_mask = CSRStorage(width) + self._falling_mask = CSRStorage(width) + self._both_mask = CSRStorage(width) + + ### + + rising_mask = self._rising_mask.storage + falling_mask = self._falling_mask.storage + both_mask = self._both_mask.storage + + dat = self.sink.dat + dat_d = Signal(width) + rising_hit = Signal() + falling_hit = Signal() + both_hit = Signal() + hit = self.source.hit + + self.sync += dat_d.eq(dat) + + self.comb += [ + rising_hit.eq(rising_mask & dat & ~dat_d), + falling_hit.eq(rising_mask & ~dat & dat_d), + both_hit.eq((both_mask & dat) != (both_mask & dat_d)), + hit.eq(rising_hit | falling_hit | both_hit), + self.source.stb.eq(self.sink.stb) + ] + +class LiteScopeSum(Module, AutoCSR): + def __init__(self, ports=4): + + self.sinks = [Record(hit_layout()) for p in range(ports)] + self.source = Record(hit_layout()) + + self._prog_we = CSRStorage() + self._prog_adr = CSRStorage(ports) #FIXME + self._prog_dat = CSRStorage() + + mem = Memory(1, 2**ports) + lut_port = mem.get_port() + prog_port = mem.get_port(write_capable=True) + + self.specials += mem, lut_port, prog_port + + ### + + # Lut prog + self.comb += [ + prog_port.we.eq(self._prog_we.storage), + prog_port.adr.eq(self._prog_adr.storage), + prog_port.dat_w.eq(self._prog_dat.storage) + ] + + # Lut read + for i, sink in enumerate(self.sinks): + self.comb += lut_port.adr[i].eq(sink.hit) + + # Drive source + self.comb += [ + self.source.stb.eq(optree("&", [sink.stb for sink in self.sinks])), + self.source.hit.eq(lut_port.dat_r), + ] + + +class LiteScopeTrigger(Module, AutoCSR): + def __init__(self, width, ports): + self.width = width + self.ports = ports + + self.submodules.sum = LiteScopeSum(len(ports)) + for i, port in enumerate(ports): + setattr(self.submodules, "port"+str(i), port) + + self.sink = Record(dat_layout(width)) + self.source = self.sum.source + + ### + + for i, port in enumerate(ports): + self.comb += [ + self.sink.connect(port.sink), + port.source.connect(self.sum.sinks[i]) + ] diff --git a/litescope/frontend/io.py b/litescope/frontend/io.py new file mode 100644 index 00000000..3fb2d318 --- /dev/null +++ b/litescope/frontend/io.py @@ -0,0 +1,10 @@ +from migen.fhdl.structure import * +from migen.bank.description import * + +class LiteScopeIO(Module, AutoCSR): + def __init__(self, width): + self._r_i = CSRStatus(width) + self._r_o = CSRStorage(width) + + self.i = self._r_i.status + self.o = self._r_o.storage diff --git a/litescope/frontend/la.py b/litescope/frontend/la.py new file mode 100644 index 00000000..a69f749f --- /dev/null +++ b/litescope/frontend/la.py @@ -0,0 +1,98 @@ +from migen.fhdl.std import * +from migen.fhdl import verilog +from migen.bank.description import * +from migen.actorlib.fifo import AsyncFIFO + +from litescope.common import * +from litescope.core.trigger import LiteScopeTrigger +from litescope.core.storage import LiteScopeRecorder, LiteScopeRunLengthEncoder + +from mibuild.tools import write_to_file + +def _getattr_all(l, attr): + it = iter(l) + r = getattr(next(it), attr) + for e in it: + if getattr(e, attr) != r: + raise ValueError + return r + +class LiteScopeLA(Module, AutoCSR): + def __init__(self, depth, dat, with_rle=False, clk_domain="sys", pipe=False): + self.depth = depth + self.with_rle = with_rle + self.clk_domain = clk_domain + self.pipe = pipe + self.ports = [] + self.width = flen(dat) + + self.stb = Signal(reset=1) + self.dat = dat + + def add_port(self, port_class): + port = port_class(self.width) + self.ports.append(port) + + def do_finalize(self): + stb = self.stb + dat = self.dat + if self.pipe: + sync = getattr(self.sync, self.clk_domain) + stb_new = Signal() + dat_new = Signal(flen(dat)) + sync += [ + stb_new.eq(stb), + dat_new.eq(dat) + ] + stb = stb_new + dat = dat_new + + if self.clk_domain is not "sys": + fifo = AsyncFIFO([("dat", self.width)], 32) + self.submodules += RenameClockDomains(fifo, {"write": self.clk_domain, "read": "sys"}) + self.comb += [ + fifo.sink.stb.eq(stb), + fifo.sink.dat.eq(dat) + ] + sink = Record(dat_layout(self.width)) + self.comb += [ + sink.stb.eq(fifo.source.stb), + sink.dat.eq(fifo.source.dat), + fifo.source.ack.eq(1) + ] + else: + sink = Record(dat_layout(self.width)) + self.comb += [ + sink.stb.eq(stb), + sink.dat.eq(dat) + ] + + self.submodules.trigger = trigger = LiteScopeTrigger(self.width, self.ports) + self.submodules.recorder = recorder = LiteScopeRecorder(self.width, self.depth) + self.comb += [ + sink.connect(trigger.sink), + trigger.source.connect(recorder.trig_sink) + ] + + if self.with_rle: + self.submodules.rle = rle = LiteScopeRunLengthEncoder(self.width) + self.comb += [ + sink.connect(rle.sink), + rle.source.connect(recorder.dat_sink) + ] + else: + self.comb += sink.connect(recorder.dat_sink) + + def export(self, design, layout, filename): + ret, ns = verilog.convert(design, return_ns=True) + r = "" + def format_line(*args): + return ",".join(args) + "\n" + + r += format_line("config", "width", str(self.width)) + r += format_line("config", "depth", str(self.depth)) + r += format_line("config", "with_rle", str(int(self.with_rle))) + + for e in layout: + r += format_line("layout", ns.get_name(e), str(flen(e))) + write_to_file(filename, r) diff --git a/litescope/host/__init__.py b/litescope/host/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/litescope/host/cpuif.py b/litescope/host/cpuif.py new file mode 100644 index 00000000..6a0ed40b --- /dev/null +++ b/litescope/host/cpuif.py @@ -0,0 +1,11 @@ +from migen.bank.description import CSRStatus + +def get_csr_csv(csr_base, bank_array): + r = "" + for name, csrs, mapaddr, rmap in bank_array.banks: + reg_base = csr_base + 0x800*mapaddr + for csr in csrs: + nr = (csr.size + 7)//8 + r += "{}_{},0x{:08x},{},{}\n".format(name, csr.name, reg_base, nr, "ro" if isinstance(csr, CSRStatus) else "rw") + reg_base += 4*nr + return r diff --git a/litescope/host/driver.py b/litescope/host/driver.py new file mode 100644 index 00000000..31645197 --- /dev/null +++ b/litescope/host/driver.py @@ -0,0 +1,230 @@ +import csv +import time +import sys +import string +import serial +from struct import * +from migen.fhdl.structure import * +from litescope.host.reg import * +from litescope.host.dump import * +from litescope.host.truthtable import * + +def write_b(uart, data): + uart.write(pack('B',data)) + +class LiteScopeUART2WBDriver: + WRITE_CMD = 0x01 + READ_CMD = 0x02 + def __init__(self, port, baudrate=115200, addrmap=None, busword=8, debug=False): + self.port = port + self.baudrate = str(baudrate) + self.debug = debug + self.uart = serial.Serial(port, baudrate, timeout=0.25) + self.regs = build_map(addrmap, busword, self.read, self.write) + + def open(self): + self.uart.flushOutput() + self.uart.close() + self.uart.open() + self.uart.flushInput() + try: + self.regs.uart2wb_sel.write(1) + except: + pass + + def close(self): + try: + self.regs.uart2wb_sel.write(0) + except: + pass + self.uart.flushOutput() + self.uart.close() + + def read(self, addr, burst_length=1): + self.uart.flushInput() + write_b(self.uart, self.READ_CMD) + write_b(self.uart, burst_length) + addr = addr//4 + write_b(self.uart, (addr & 0xff000000) >> 24) + write_b(self.uart, (addr & 0x00ff0000) >> 16) + write_b(self.uart, (addr & 0x0000ff00) >> 8) + write_b(self.uart, (addr & 0x000000ff)) + values = [] + for i in range(burst_length): + val = 0 + for j in range(4): + val = val << 8 + val |= ord(self.uart.read()) + if self.debug: + print("RD %08X @ %08X" %(val, (addr+i)*4)) + values.append(val) + if burst_length == 1: + return values[0] + else: + return values + + def write(self, addr, data): + if isinstance(data, list): + burst_length = len(data) + else: + burst_length = 1 + write_b(self.uart, self.WRITE_CMD) + write_b(self.uart, burst_length) + addr = addr//4 + write_b(self.uart, (addr & 0xff000000) >> 24) + write_b(self.uart, (addr & 0x00ff0000) >> 16) + write_b(self.uart, (addr & 0x0000ff00) >> 8) + write_b(self.uart, (addr & 0x000000ff)) + if isinstance(data, list): + for i in range(len(data)): + dat = data[i] + for j in range(4): + write_b(self.uart, (dat & 0xff000000) >> 24) + dat = dat << 8 + if self.debug: + print("WR %08X @ %08X" %(data[i], (addr + i)*4)) + else: + dat = data + for j in range(4): + write_b(self.uart, (dat & 0xff000000) >> 24) + dat = dat << 8 + if self.debug: + print("WR %08X @ %08X" %(data, (addr * 4))) + +class LiteScopeIODriver(): + def __init__(self, regs, name): + self.regs = regs + self.name = name + self.build() + + def build(self): + for key, value in self.regs.d.items(): + if self.name in key: + key.replace(self.name +"_") + setattr(self, key, value) + + def write(self, value): + self.o.write(value) + + def read(self): + return self.i.read() + +class LiteScopeLADriver(): + def __init__(self, regs, name, config_csv=None, use_rle=False): + self.regs = regs + self.name = name + self.use_rle = use_rle + if config_csv is None: + self.config_csv = name + ".csv" + self.get_config() + self.get_layout() + self.build() + self.dat = Dat(self.width) + + def get_config(self): + csv_reader = csv.reader(open(self.config_csv), delimiter=',', quotechar='#') + for item in csv_reader: + t, n, v = item + if t == "config": + setattr(self, n, int(v)) + + def get_layout(self): + self.layout = [] + csv_reader = csv.reader(open(self.config_csv), delimiter=',', quotechar='#') + for item in csv_reader: + t, n, v = item + if t == "layout": + self.layout.append((n, int(v))) + + def build(self): + for key, value in self.regs.d.items(): + if self.name == key[:len(self.name)]: + key.replace(self.name + "_") + setattr(self, key, value) + value = 1 + for name, length in self.layout: + setattr(self, name + "_o", value) + value = value*(2**length) + value = 0 + for name, length in self.layout: + setattr(self, name + "_m", (2**length-1) << value) + value += length + + def show_state(self, s): + print(s, end="|") + sys.stdout.flush() + + def prog_term(self, port, trigger=0, mask=0, cond=None): + if cond is not None: + for k, v in cond.items(): + trigger |= getattr(self, k + "_o")*v + mask |= getattr(self, k + "_m") + t = getattr(self, "trigger_port{d}_trig".format(d=int(port))) + m = getattr(self, "trigger_port{d}_mask".format(d=int(port))) + t.write(trigger) + m.write(mask) + + def prog_range_detector(self, port, low, high): + l = getattr(self, "trigger_port{d}_low".format(d=int(port))) + h = getattr(self, "trigger_port{d}_high".format(d=int(port))) + l.write(low) + h.write(high) + + def prog_edge_detector(self, port, rising_mask, falling_mask, both_mask): + rm = getattr(self, "trigger_port{d}_rising_mask".format(d=int(port))) + fm = getattr(self, "trigger_port{d}_falling_mask".format(d=int(port))) + bm = getattr(self, "trigger_port{d}_both_mask".format(d=int(port))) + rm.write(rising_mask) + fm.write(falling_mask) + bm.write(both_mask) + + def prog_sum(self, equation): + datas = gen_truth_table(equation) + for adr, dat in enumerate(datas): + self.trigger_sum_prog_adr.write(adr) + self.trigger_sum_prog_dat.write(dat) + self.trigger_sum_prog_we.write(1) + + def config_rle(self, v): + self.rle_enable.write(v) + + def is_done(self): + return self.recorder_done.read() + + def wait_done(self): + self.show_state("WAIT HIT") + while(not self.is_done()): + time.sleep(0.1) + + def trigger(self, offset, length): + self.show_state("TRIG") + if self.with_rle: + self.config_rle(self.use_rle) + self.recorder_offset.write(offset) + self.recorder_length.write(length) + self.recorder_trigger.write(1) + + def read(self): + self.show_state("READ") + empty = self.recorder_read_empty.read() + while(not empty): + self.dat.append(self.recorder_read_dat.read()) + empty = self.recorder_read_empty.read() + self.recorder_read_en.write(1) + if self.with_rle: + if self.use_rle: + self.dat = self.dat.decode_rle() + return self.dat + + def export(self, export_fn=None): + self.show_state("EXPORT") + dump = Dump() + dump.add_from_layout(self.layout, self.dat) + if ".vcd" in export_fn: + VCDExport(dump).write(export_fn) + elif ".csv" in export_fn: + CSVExport(dump).write(export_fn) + elif ".py" in export_fn: + PYExport(dump).write(export_fn) + else: + raise NotImplementedError diff --git a/litescope/host/dump.py b/litescope/host/dump.py new file mode 100644 index 00000000..77a6e309 --- /dev/null +++ b/litescope/host/dump.py @@ -0,0 +1,311 @@ +import sys +import datetime + +def dec2bin(d, nb=0): + if d=="x": + return "x"*nb + elif d==0: + b="0" + else: + b="" + while d!=0: + b="01"[d&1]+b + d=d>>1 + return b.zfill(nb) + +def get_bits(values, width, low, high=None): + r = [] + for val in values: + t = dec2bin(val, width)[::-1] + if high == None: + t = t[low] + else: + t = t[low:high] + t = t[::-1] + t = int(t,2) + r.append(t) + return r + +class Dat(list): + def __init__(self, width): + self.width = width + + def __getitem__(self, key): + if isinstance(key, int): + return get_bits(self, self.width, key) + elif isinstance(key, slice): + if key.start != None: + start = key.start + else: + start = 0 + if key.stop != None: + stop = key.stop + else: + stop = self.width + if stop > self.width: + stop = self.width + if key.step != None: + raise KeyError + return get_bits(self, self.width, start, stop) + else: + raise KeyError + + def decode_rle(self): + rle_bit = self[-1] + rle_dat = self[:self.width-1] + + dat = Dat(self.width) + i=0 + last = 0 + for d in self: + if rle_bit[i]: + if len(dat) >= 1: + # FIX ME... why is rle_dat in reverse order... + for j in range(int(dec2bin(rle_dat[i])[::-1],2)): + dat.append(last) + else: + dat.append(d) + last = d + i +=1 + return dat + +class Var: + def __init__(self, name, width, values=[], type="wire", default="x"): + self.type = type + self.width = width + self.name = name + self.val = default + self.values = values + self.vcd_id = None + + def set_vcd_id(self, s): + self.vcd_id = s + + def __len__(self): + return len(self.values) + + def change(self, cnt): + r = "" + try : + if self.values[cnt+1] != self.val: + r += "b" + r += dec2bin(self.values[cnt+1], self.width) + r += " " + r += self.vcd_id + r += "\n" + return r + except : + return r + return r + +class Dump: + def __init__(self): + self.vars = [] + self.vcd_id = "!" + + def add(self, var): + var.set_vcd_id(self.vcd_id) + self.vcd_id = chr(ord(self.vcd_id)+1) + self.vars.append(var) + + def add_from_layout(self, layout, var): + i=0 + for s, n in layout: + self.add(Var(s, n, var[i:i+n])) + i += n + + def __len__(self): + l = 0 + for var in self.vars: + l = max(len(var),l) + return l + +class VCDExport(): + def __init__(self, dump, timescale="1ps", comment=""): + self.dump = dump + self.timescale = timescale + self.comment = comment + self.cnt = -1 + + def change(self): + r = "" + c = "" + for var in self.dump.vars: + c += var.change(self.cnt) + if c != "": + r += "#" + r += str(self.cnt+1) + r += "\n" + r += c + return r + + def p_date(self): + now = datetime.datetime.now() + r = "$date\n" + r += "\t" + r += now.strftime("%Y-%m-%d %H:%M") + r += "\n" + r += "$end\n" + return r + + def p_version(self): + r = "$version\n" + r += "\tmiscope VCD dump\n" + r += "$end\n" + return r + + def p_comment(self): + r = "$comment\n" + r += self.comment + r += "\n$end\n" + return r + + def p_timescale(self): + r = "$timescale " + r += self.timescale + r += " $end\n" + return r + + def p_scope(self): + r = "$scope " + r += self.timescale + r += " $end\n" + return r + + def p_vars(self): + r = "" + for var in self.dump.vars: + r += "$var " + r += var.type + r += " " + r += str(var.width) + r += " " + r += var.vcd_id + r += " " + r += var.name + r += " $end\n" + return r + + def p_unscope(self): + r = "$unscope " + r += " $end\n" + return r + + def p_enddefinitions(self): + r = "$enddefinitions " + r += " $end\n" + return r + + def p_dumpvars(self): + r = "$dumpvars\n" + for var in self.dump.vars: + r += "b" + r += dec2bin(var.val, var.width) + r += " " + r += var.vcd_id + r+= "\n" + r += "$end\n" + return r + + def p_valuechange(self): + r = "" + for i in range(len(self.dump)): + r += self.change() + self.cnt += 1 + return r + + def __repr__(self): + r = "" + r += self.p_date() + r += self.p_version() + r += self.p_comment() + r += self.p_timescale() + r += self.p_scope() + r += self.p_vars() + r += self.p_unscope() + r += self.p_enddefinitions() + r += self.p_dumpvars() + r += self.p_valuechange() + return r + + def write(self, filename): + f = open(filename, "w") + f.write(str(self)) + f.close() + +class CSVExport(): + def __init__(self, dump): + self.dump = dump + + def p_vars(self): + r = "" + for var in self.dump.vars: + r += var.name + r += "," + r += "\n" + for var in self.dump.vars: + r += str(var.width) + r += "," + r += "\n" + return r + + def p_dumpvars(self): + r = "" + for i in range(len(self.dump)): + for var in self.dump.vars: + try: + var.val = var.values[i] + except: + pass + if var.val == "x": + r += "x" + else: + r += dec2bin(var.val, var.width) + r += ", " + r+= "\n" + return r + + def __repr__(self): + r = "" + r += self.p_vars() + r += self.p_dumpvars() + return r + + def write(self, filename): + f = open(filename, "w") + f.write(str(self)) + f.close() + +class PYExport(): + def __init__(self, dump): + self.dump = dump + + def __repr__(self): + r = "dump = {\n" + for var in self.dump.vars: + r += "\"" + var.name + "\"" + r += " : " + r += str(var.values) + r += ",\n" + r += "}" + return r + + def write(self, filename): + f = open(filename, "w") + f.write(str(self)) + f.close() + +def main(): + dump = Dump() + dump.add(Var("foo1", 1, [0,1,0,1,0,1])) + dump.add(Var("foo2", 2, [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0])) + ramp = [i%128 for i in range(1024)] + dump.add(Var("ramp", 16, ramp)) + + VCDExport(dump).write("mydump.vcd") + CSVExport(dump).write("mydump.csv") + PYExport(dump).write("mydump.py") + +if __name__ == '__main__': + main() + diff --git a/litescope/host/reg.py b/litescope/host/reg.py new file mode 100644 index 00000000..16a98d91 --- /dev/null +++ b/litescope/host/reg.py @@ -0,0 +1,48 @@ +import csv + +class MappedReg: + def __init__(self, readfn, writefn, name, addr, length, busword, mode): + self.readfn = readfn + self.writefn = writefn + self.addr = addr + self.length = length + self.busword = busword + self.mode = mode + + def read(self): + if self.mode not in ["rw", "ro"]: + raise KeyError(name + "register not readable") + r = 0 + for i in range(self.length): + r |= self.readfn(self.addr + 4*i) + if i != (self.length-1): + r <<= self.busword + return r + + def write(self, value): + if self.mode not in ["rw", "wo"]: + raise KeyError(name + "register not writable") + for i in range(self.length): + dat = (value >> ((self.length-1-i)*self.busword)) & (2**self.busword-1) + self.writefn(self.addr + 4*i, dat) + +class MappedRegs: + def __init__(self, d): + self.d = d + + def __getattr__(self, attr): + try: + return self.__dict__['d'][attr] + except KeyError: + pass + raise KeyError("No such register " + attr) + +def build_map(addrmap, busword, readfn, writefn): + csv_reader = csv.reader(open(addrmap), delimiter=',', quotechar='#') + d = {} + for item in csv_reader: + name, addr, length, mode = item + addr = int(addr.replace("0x", ""), 16) + length = int(length) + d[name] = MappedReg(readfn, writefn, name, addr, length, busword, mode) + return MappedRegs(d) \ No newline at end of file diff --git a/litescope/host/truthtable.py b/litescope/host/truthtable.py new file mode 100644 index 00000000..319eaee7 --- /dev/null +++ b/litescope/host/truthtable.py @@ -0,0 +1,47 @@ +import os +import re +import sys + +def is_number(x): + try: + _ = float(x) + except ValueError: + return False + return True + +def remove_numbers(seq): + return [x for x in seq if not is_number(x)] + +def remove_duplicates(seq): + seen = set() + seen_add = seen.add + return [x for x in seq if x not in seen and not seen_add(x)] + +def get_operands(s): + operands = re.findall("[A-z0-9_]+", s) + operands = remove_duplicates(operands) + operands = remove_numbers(operands) + return sorted(operands) + +def gen_truth_table(s): + operands = get_operands(s) + width = len(operands) + stim = [] + for i in range(width): + stim_op = [] + for j in range(2**width): + stim_op.append((int(j/(2**i)))%2) + stim.append(stim_op) + + truth_table = [] + for i in range(2**width): + for j in range(width): + exec("%s = stim[j][i]" %operands[j]) + truth_table.append(eval(s) != 0) + return truth_table + +def main(): + print(gen_truth_table("(A&B&C)|D")) + +if __name__ == '__main__': + main() diff --git a/make.py b/make.py new file mode 100644 index 00000000..987f450e --- /dev/null +++ b/make.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 + +import sys, os, argparse, subprocess, struct, importlib + +from mibuild.tools import write_to_file +from migen.util.misc import autotype +from migen.fhdl import verilog, edif +from migen.fhdl.structure import _Fragment +from mibuild import tools +from mibuild.xilinx_common import * + +from misoclib.gensoc import cpuif + +from litesata.common import * + +def _import(default, name): + return importlib.import_module(default + "." + name) + +def _get_args(): + parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, + description="""\ +LiteScope - based on Migen. + +This program builds and/or loads LiteSATA components. +One or several actions can be specified: + +clean delete previous build(s). +build-rtl build verilog rtl. +build-bitstream build-bitstream build FPGA bitstream. +build-csr-csv save CSR map into CSV file. + +load-bitstream load bitstream into volatile storage. + +all clean, build-csr-csv, build-bitstream, load-bitstream. +""") + + parser.add_argument("-t", "--target", default="simple", help="Core type to build") + parser.add_argument("-s", "--sub-target", default="", help="variant of the Core type to build") + parser.add_argument("-p", "--platform", default=None, help="platform to build for") + parser.add_argument("-Ot", "--target-option", default=[], nargs=2, action="append", help="set target-specific option") + parser.add_argument("-Op", "--platform-option", default=[], nargs=2, action="append", help="set platform-specific option") + parser.add_argument("--csr_csv", default="./test/csr.csv", help="CSV file to save the CSR map into") + + parser.add_argument("action", nargs="+", help="specify an action") + + return parser.parse_args() + +# Note: misoclib need to be installed as a python library + +if __name__ == "__main__": + args = _get_args() + + # create top-level Core object + target_module = _import("targets", args.target) + if args.sub_target: + top_class = getattr(target_module, args.sub_target) + else: + top_class = target_module.default_subtarget + + if args.platform is None: + platform_name = top_class.default_platform + else: + platform_name = args.platform + platform_module = _import("mibuild.platforms", platform_name) + platform_kwargs = dict((k, autotype(v)) for k, v in args.platform_option) + platform = platform_module.Platform(**platform_kwargs) + + build_name = top_class.__name__.lower() + "-" + platform_name + top_kwargs = dict((k, autotype(v)) for k, v in args.target_option) + soc = top_class(platform, **top_kwargs) + soc.finalize() + + # decode actions + action_list = ["clean", "build-csr-csv", "build-bitstream", "load-bitstream", "all"] + actions = {k: False for k in action_list} + for action in args.action: + if action in actions: + actions[action] = True + else: + print("Unknown action: "+action+". Valid actions are:") + for a in action_list: + print(" "+a) + sys.exit(1) + + print(""" + __ _ __ ____ + / / (_) /____ / __/______ ___ ___ + / /__/ / __/ -_)\ \/ __/ _ \/ _ \/ -_) + /____/_/\__/\__/___/\__/\___/ .__/\__/ + /_/ + + A small footprint and configurable embedded FPGA + based in Migen/MiSoC + +====== Building options: ====== +===============================""".format() +) + + # dependencies + if actions["all"]: + actions["clean"] = True + actions["build-csr-csv"] = True + actions["build-bitstream"] = True + actions["load-bitstream"] = True + + if actions["build-bitstream"]: + actions["clean"] = True + actions["build-csr-csv"] = True + actions["build-bitstream"] = True + actions["load-bitstream"] = True + + if actions["clean"]: + subprocess.call(["rm", "-rf", "build/*"]) + + if actions["build-csr-csv"]: + csr_csv = cpuif.get_csr_csv(soc.cpu_csr_regions) + write_to_file(args.csr_csv, csr_csv) + + if actions["build-bitstream"]: + platform.build(soc, build_name=build_name) + + if actions["load-bitstream"]: + prog = platform.create_programmer() + prog.load_bitstream("build/" + build_name + platform.bitstream_ext) diff --git a/miscope/__init__.py b/miscope/__init__.py deleted file mode 100644 index 03249cdb..00000000 --- a/miscope/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -try: - from miscope.miio import MiIo - from miscope.mila import MiLa - from miscope.trigger import Term, RangeDetector, EdgeDetector - from miscope.uart2wishbone import UART2Wishbone -except: - pass \ No newline at end of file diff --git a/miscope/host/__init__.py b/miscope/host/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/miscope/host/cpuif.py b/miscope/host/cpuif.py deleted file mode 100644 index 6a0ed40b..00000000 --- a/miscope/host/cpuif.py +++ /dev/null @@ -1,11 +0,0 @@ -from migen.bank.description import CSRStatus - -def get_csr_csv(csr_base, bank_array): - r = "" - for name, csrs, mapaddr, rmap in bank_array.banks: - reg_base = csr_base + 0x800*mapaddr - for csr in csrs: - nr = (csr.size + 7)//8 - r += "{}_{},0x{:08x},{},{}\n".format(name, csr.name, reg_base, nr, "ro" if isinstance(csr, CSRStatus) else "rw") - reg_base += 4*nr - return r diff --git a/miscope/host/drivers.py b/miscope/host/drivers.py deleted file mode 100644 index 44084646..00000000 --- a/miscope/host/drivers.py +++ /dev/null @@ -1,143 +0,0 @@ -import csv -import time -import sys -from miscope.host.dump import * -from miscope.host.truthtable import * - -class MiIoDriver(): - def __init__(self, regs, name): - self.regs = regs - self.name = name - self.build_miio() - - def build_miio(self): - for key, value in self.regs.d.items(): - if self.name in key: - key.replace(self.name, "miio") - setattr(self, key, value) - - def write(self, value): - self.miio_o.write(value) - - def read(self): - return self.miio_i.read() - -class MiLaDriver(): - def __init__(self, regs, name, config_csv=None, use_rle=False): - self.regs = regs - self.name = name - self.use_rle = use_rle - if config_csv is None: - self.config_csv = name + ".csv" - self.get_config() - self.get_layout() - self.build_mila() - self.dat = Dat(self.width) - - def get_config(self): - csv_reader = csv.reader(open(self.config_csv), delimiter=',', quotechar='#') - for item in csv_reader: - t, n, v = item - if t == "config": - setattr(self, n, int(v)) - - def get_layout(self): - self.layout = [] - csv_reader = csv.reader(open(self.config_csv), delimiter=',', quotechar='#') - for item in csv_reader: - t, n, v = item - if t == "layout": - self.layout.append((n, int(v))) - - def build_mila(self): - for key, value in self.regs.d.items(): - if self.name == key[:len(self.name)]: - key.replace(self.name, "mila") - setattr(self, key, value) - value = 1 - for name, length in self.layout: - setattr(self, name+"_o", value) - value = value*(2**length) - value = 0 - for name, length in self.layout: - setattr(self, name+"_m", (2**length-1) << value) - value += length - - def show_state(self, s): - print(s, end="|") - sys.stdout.flush() - - def prog_term(self, port, trigger=0, mask=0, cond=None): - if cond is not None: - for k, v in cond.items(): - trigger |= getattr(self, k+"_o")*v - mask |= getattr(self, k+"_m") - t = getattr(self, "mila_trigger_port{d}_trig".format(d=int(port))) - m = getattr(self, "mila_trigger_port{d}_mask".format(d=int(port))) - t.write(trigger) - m.write(mask) - - def prog_range_detector(self, port, low, high): - l = getattr(self, "mila_trigger_port{d}_low".format(d=int(port))) - h = getattr(self, "mila_trigger_port{d}_high".format(d=int(port))) - l.write(low) - h.write(high) - - def prog_edge_detector(self, port, rising_mask, falling_mask, both_mask): - rm = getattr(self, "mila_trigger_port{d}_rising_mask".format(d=int(port))) - fm = getattr(self, "mila_trigger_port{d}_falling_mask".format(d=int(port))) - bm = getattr(self, "mila_trigger_port{d}_both_mask".format(d=int(port))) - rm.write(rising_mask) - fm.write(falling_mask) - bm.write(both_mask) - - def prog_sum(self, equation): - datas = gen_truth_table(equation) - for adr, dat in enumerate(datas): - self.mila_trigger_sum_prog_adr.write(adr) - self.mila_trigger_sum_prog_dat.write(dat) - self.mila_trigger_sum_prog_we.write(1) - - def config_rle(self, v): - self.mila_rle_enable.write(v) - - def is_done(self): - return self.mila_recorder_done.read() - - def wait_done(self): - self.show_state("WAIT HIT") - while(not self.is_done()): - time.sleep(0.1) - - def trigger(self, offset, length): - self.show_state("TRIG") - if self.with_rle: - self.config_rle(self.use_rle) - self.mila_recorder_offset.write(offset) - self.mila_recorder_length.write(length) - self.mila_recorder_trigger.write(1) - - def read(self): - self.show_state("READ") - empty = self.mila_recorder_read_empty.read() - while(not empty): - self.dat.append(self.mila_recorder_read_dat.read()) - empty = self.mila_recorder_read_empty.read() - self.mila_recorder_read_en.write(1) - if self.with_rle: - if self.use_rle: - self.dat = self.dat.decode_rle() - return self.dat - - def export(self, export_fn=None): - self.show_state("EXPORT") - dump = Dump() - dump.add_from_layout(self.layout, self.dat) - if ".vcd" in export_fn: - VCDExport(dump).write(export_fn) - elif ".csv" in export_fn: - CSVExport(dump).write(export_fn) - elif ".py" in export_fn: - PYExport(dump).write(export_fn) - else: - raise NotImplementedError diff --git a/miscope/host/dump.py b/miscope/host/dump.py deleted file mode 100644 index 77a6e309..00000000 --- a/miscope/host/dump.py +++ /dev/null @@ -1,311 +0,0 @@ -import sys -import datetime - -def dec2bin(d, nb=0): - if d=="x": - return "x"*nb - elif d==0: - b="0" - else: - b="" - while d!=0: - b="01"[d&1]+b - d=d>>1 - return b.zfill(nb) - -def get_bits(values, width, low, high=None): - r = [] - for val in values: - t = dec2bin(val, width)[::-1] - if high == None: - t = t[low] - else: - t = t[low:high] - t = t[::-1] - t = int(t,2) - r.append(t) - return r - -class Dat(list): - def __init__(self, width): - self.width = width - - def __getitem__(self, key): - if isinstance(key, int): - return get_bits(self, self.width, key) - elif isinstance(key, slice): - if key.start != None: - start = key.start - else: - start = 0 - if key.stop != None: - stop = key.stop - else: - stop = self.width - if stop > self.width: - stop = self.width - if key.step != None: - raise KeyError - return get_bits(self, self.width, start, stop) - else: - raise KeyError - - def decode_rle(self): - rle_bit = self[-1] - rle_dat = self[:self.width-1] - - dat = Dat(self.width) - i=0 - last = 0 - for d in self: - if rle_bit[i]: - if len(dat) >= 1: - # FIX ME... why is rle_dat in reverse order... - for j in range(int(dec2bin(rle_dat[i])[::-1],2)): - dat.append(last) - else: - dat.append(d) - last = d - i +=1 - return dat - -class Var: - def __init__(self, name, width, values=[], type="wire", default="x"): - self.type = type - self.width = width - self.name = name - self.val = default - self.values = values - self.vcd_id = None - - def set_vcd_id(self, s): - self.vcd_id = s - - def __len__(self): - return len(self.values) - - def change(self, cnt): - r = "" - try : - if self.values[cnt+1] != self.val: - r += "b" - r += dec2bin(self.values[cnt+1], self.width) - r += " " - r += self.vcd_id - r += "\n" - return r - except : - return r - return r - -class Dump: - def __init__(self): - self.vars = [] - self.vcd_id = "!" - - def add(self, var): - var.set_vcd_id(self.vcd_id) - self.vcd_id = chr(ord(self.vcd_id)+1) - self.vars.append(var) - - def add_from_layout(self, layout, var): - i=0 - for s, n in layout: - self.add(Var(s, n, var[i:i+n])) - i += n - - def __len__(self): - l = 0 - for var in self.vars: - l = max(len(var),l) - return l - -class VCDExport(): - def __init__(self, dump, timescale="1ps", comment=""): - self.dump = dump - self.timescale = timescale - self.comment = comment - self.cnt = -1 - - def change(self): - r = "" - c = "" - for var in self.dump.vars: - c += var.change(self.cnt) - if c != "": - r += "#" - r += str(self.cnt+1) - r += "\n" - r += c - return r - - def p_date(self): - now = datetime.datetime.now() - r = "$date\n" - r += "\t" - r += now.strftime("%Y-%m-%d %H:%M") - r += "\n" - r += "$end\n" - return r - - def p_version(self): - r = "$version\n" - r += "\tmiscope VCD dump\n" - r += "$end\n" - return r - - def p_comment(self): - r = "$comment\n" - r += self.comment - r += "\n$end\n" - return r - - def p_timescale(self): - r = "$timescale " - r += self.timescale - r += " $end\n" - return r - - def p_scope(self): - r = "$scope " - r += self.timescale - r += " $end\n" - return r - - def p_vars(self): - r = "" - for var in self.dump.vars: - r += "$var " - r += var.type - r += " " - r += str(var.width) - r += " " - r += var.vcd_id - r += " " - r += var.name - r += " $end\n" - return r - - def p_unscope(self): - r = "$unscope " - r += " $end\n" - return r - - def p_enddefinitions(self): - r = "$enddefinitions " - r += " $end\n" - return r - - def p_dumpvars(self): - r = "$dumpvars\n" - for var in self.dump.vars: - r += "b" - r += dec2bin(var.val, var.width) - r += " " - r += var.vcd_id - r+= "\n" - r += "$end\n" - return r - - def p_valuechange(self): - r = "" - for i in range(len(self.dump)): - r += self.change() - self.cnt += 1 - return r - - def __repr__(self): - r = "" - r += self.p_date() - r += self.p_version() - r += self.p_comment() - r += self.p_timescale() - r += self.p_scope() - r += self.p_vars() - r += self.p_unscope() - r += self.p_enddefinitions() - r += self.p_dumpvars() - r += self.p_valuechange() - return r - - def write(self, filename): - f = open(filename, "w") - f.write(str(self)) - f.close() - -class CSVExport(): - def __init__(self, dump): - self.dump = dump - - def p_vars(self): - r = "" - for var in self.dump.vars: - r += var.name - r += "," - r += "\n" - for var in self.dump.vars: - r += str(var.width) - r += "," - r += "\n" - return r - - def p_dumpvars(self): - r = "" - for i in range(len(self.dump)): - for var in self.dump.vars: - try: - var.val = var.values[i] - except: - pass - if var.val == "x": - r += "x" - else: - r += dec2bin(var.val, var.width) - r += ", " - r+= "\n" - return r - - def __repr__(self): - r = "" - r += self.p_vars() - r += self.p_dumpvars() - return r - - def write(self, filename): - f = open(filename, "w") - f.write(str(self)) - f.close() - -class PYExport(): - def __init__(self, dump): - self.dump = dump - - def __repr__(self): - r = "dump = {\n" - for var in self.dump.vars: - r += "\"" + var.name + "\"" - r += " : " - r += str(var.values) - r += ",\n" - r += "}" - return r - - def write(self, filename): - f = open(filename, "w") - f.write(str(self)) - f.close() - -def main(): - dump = Dump() - dump.add(Var("foo1", 1, [0,1,0,1,0,1])) - dump.add(Var("foo2", 2, [1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0])) - ramp = [i%128 for i in range(1024)] - dump.add(Var("ramp", 16, ramp)) - - VCDExport(dump).write("mydump.vcd") - CSVExport(dump).write("mydump.csv") - PYExport(dump).write("mydump.py") - -if __name__ == '__main__': - main() - diff --git a/miscope/host/regs.py b/miscope/host/regs.py deleted file mode 100644 index 16a98d91..00000000 --- a/miscope/host/regs.py +++ /dev/null @@ -1,48 +0,0 @@ -import csv - -class MappedReg: - def __init__(self, readfn, writefn, name, addr, length, busword, mode): - self.readfn = readfn - self.writefn = writefn - self.addr = addr - self.length = length - self.busword = busword - self.mode = mode - - def read(self): - if self.mode not in ["rw", "ro"]: - raise KeyError(name + "register not readable") - r = 0 - for i in range(self.length): - r |= self.readfn(self.addr + 4*i) - if i != (self.length-1): - r <<= self.busword - return r - - def write(self, value): - if self.mode not in ["rw", "wo"]: - raise KeyError(name + "register not writable") - for i in range(self.length): - dat = (value >> ((self.length-1-i)*self.busword)) & (2**self.busword-1) - self.writefn(self.addr + 4*i, dat) - -class MappedRegs: - def __init__(self, d): - self.d = d - - def __getattr__(self, attr): - try: - return self.__dict__['d'][attr] - except KeyError: - pass - raise KeyError("No such register " + attr) - -def build_map(addrmap, busword, readfn, writefn): - csv_reader = csv.reader(open(addrmap), delimiter=',', quotechar='#') - d = {} - for item in csv_reader: - name, addr, length, mode = item - addr = int(addr.replace("0x", ""), 16) - length = int(length) - d[name] = MappedReg(readfn, writefn, name, addr, length, busword, mode) - return MappedRegs(d) \ No newline at end of file diff --git a/miscope/host/truthtable.py b/miscope/host/truthtable.py deleted file mode 100644 index 319eaee7..00000000 --- a/miscope/host/truthtable.py +++ /dev/null @@ -1,47 +0,0 @@ -import os -import re -import sys - -def is_number(x): - try: - _ = float(x) - except ValueError: - return False - return True - -def remove_numbers(seq): - return [x for x in seq if not is_number(x)] - -def remove_duplicates(seq): - seen = set() - seen_add = seen.add - return [x for x in seq if x not in seen and not seen_add(x)] - -def get_operands(s): - operands = re.findall("[A-z0-9_]+", s) - operands = remove_duplicates(operands) - operands = remove_numbers(operands) - return sorted(operands) - -def gen_truth_table(s): - operands = get_operands(s) - width = len(operands) - stim = [] - for i in range(width): - stim_op = [] - for j in range(2**width): - stim_op.append((int(j/(2**i)))%2) - stim.append(stim_op) - - truth_table = [] - for i in range(2**width): - for j in range(width): - exec("%s = stim[j][i]" %operands[j]) - truth_table.append(eval(s) != 0) - return truth_table - -def main(): - print(gen_truth_table("(A&B&C)|D")) - -if __name__ == '__main__': - main() diff --git a/miscope/host/uart2wishbone.py b/miscope/host/uart2wishbone.py deleted file mode 100644 index e9590866..00000000 --- a/miscope/host/uart2wishbone.py +++ /dev/null @@ -1,87 +0,0 @@ -import string -import serial -from struct import * -from migen.fhdl.structure import * -from miscope.host.regs import * - -def write_b(uart, data): - uart.write(pack('B',data)) - -class Uart2Wishbone: - WRITE_CMD = 0x01 - READ_CMD = 0x02 - def __init__(self, port, baudrate=115200, addrmap=None, busword=8, debug=False): - self.port = port - self.baudrate = str(baudrate) - self.debug = debug - self.uart = serial.Serial(port, baudrate, timeout=0.25) - self.regs = build_map(addrmap, busword, self.read, self.write) - - def open(self): - self.uart.flushOutput() - self.uart.close() - self.uart.open() - self.uart.flushInput() - try: - self.regs.uart2wb_sel.write(1) - except: - pass - - def close(self): - try: - self.regs.uart2wb_sel.write(0) - except: - pass - self.uart.flushOutput() - self.uart.close() - - def read(self, addr, burst_length=1): - self.uart.flushInput() - write_b(self.uart, self.READ_CMD) - write_b(self.uart, burst_length) - addr = addr//4 - write_b(self.uart, (addr & 0xff000000) >> 24) - write_b(self.uart, (addr & 0x00ff0000) >> 16) - write_b(self.uart, (addr & 0x0000ff00) >> 8) - write_b(self.uart, (addr & 0x000000ff)) - values = [] - for i in range(burst_length): - val = 0 - for j in range(4): - val = val << 8 - val |= ord(self.uart.read()) - if self.debug: - print("RD %08X @ %08X" %(val, (addr+i)*4)) - values.append(val) - if burst_length == 1: - return values[0] - else: - return values - - def write(self, addr, data): - if isinstance(data, list): - burst_length = len(data) - else: - burst_length = 1 - write_b(self.uart, self.WRITE_CMD) - write_b(self.uart, burst_length) - addr = addr//4 - write_b(self.uart, (addr & 0xff000000) >> 24) - write_b(self.uart, (addr & 0x00ff0000) >> 16) - write_b(self.uart, (addr & 0x0000ff00) >> 8) - write_b(self.uart, (addr & 0x000000ff)) - if isinstance(data, list): - for i in range(len(data)): - dat = data[i] - for j in range(4): - write_b(self.uart, (dat & 0xff000000) >> 24) - dat = dat << 8 - if self.debug: - print("WR %08X @ %08X" %(data[i], (addr + i)*4)) - else: - dat = data - for j in range(4): - write_b(self.uart, (dat & 0xff000000) >> 24) - dat = dat << 8 - if self.debug: - print("WR %08X @ %08X" %(data, (addr * 4))) diff --git a/miscope/miio.py b/miscope/miio.py deleted file mode 100644 index a78c056b..00000000 --- a/miscope/miio.py +++ /dev/null @@ -1,10 +0,0 @@ -from migen.fhdl.structure import * -from migen.bank.description import * - -class MiIo(Module, AutoCSR): - def __init__(self, width): - self._r_i = CSRStatus(width) - self._r_o = CSRStorage(width) - - self.i = self._r_i.status - self.o = self._r_o.storage diff --git a/miscope/mila.py b/miscope/mila.py deleted file mode 100644 index abf8e79c..00000000 --- a/miscope/mila.py +++ /dev/null @@ -1,98 +0,0 @@ -from migen.fhdl.std import * -from migen.fhdl import verilog -from migen.bank.description import * -from migen.actorlib.fifo import AsyncFIFO - -from miscope.std import * -from miscope.trigger import Trigger -from miscope.storage import Recorder, RunLengthEncoder - -from mibuild.tools import write_to_file - -def _getattr_all(l, attr): - it = iter(l) - r = getattr(next(it), attr) - for e in it: - if getattr(e, attr) != r: - raise ValueError - return r - -class MiLa(Module, AutoCSR): - def __init__(self, depth, dat, with_rle=False, clk_domain="sys", pipe=False): - self.depth = depth - self.with_rle = with_rle - self.clk_domain = clk_domain - self.pipe = pipe - self.ports = [] - self.width = flen(dat) - - self.stb = Signal(reset=1) - self.dat = dat - - def add_port(self, port_class): - port = port_class(self.width) - self.ports.append(port) - - def do_finalize(self): - stb = self.stb - dat = self.dat - if self.pipe: - sync = getattr(self.sync, self.clk_domain) - stb_new = Signal() - dat_new = Signal(flen(dat)) - sync += [ - stb_new.eq(stb), - dat_new.eq(dat) - ] - stb = stb_new - dat = dat_new - - if self.clk_domain is not "sys": - fifo = AsyncFIFO([("dat", self.width)], 32) - self.submodules += RenameClockDomains(fifo, {"write": self.clk_domain, "read": "sys"}) - self.comb += [ - fifo.sink.stb.eq(stb), - fifo.sink.dat.eq(dat) - ] - sink = Record(dat_layout(self.width)) - self.comb += [ - sink.stb.eq(fifo.source.stb), - sink.dat.eq(fifo.source.dat), - fifo.source.ack.eq(1) - ] - else: - sink = Record(dat_layout(self.width)) - self.comb += [ - sink.stb.eq(stb), - sink.dat.eq(dat) - ] - - self.submodules.trigger = trigger = Trigger(self.width, self.ports) - self.submodules.recorder = recorder = Recorder(self.width, self.depth) - self.comb += [ - sink.connect(trigger.sink), - trigger.source.connect(recorder.trig_sink) - ] - - if self.with_rle: - self.submodules.rle = rle = RunLengthEncoder(self.width) - self.comb += [ - sink.connect(rle.sink), - rle.source.connect(recorder.dat_sink) - ] - else: - self.comb += sink.connect(recorder.dat_sink) - - def export(self, design, layout, filename): - ret, ns = verilog.convert(design, return_ns=True) - r = "" - def format_line(*args): - return ",".join(args) + "\n" - - r += format_line("config", "width", str(self.width)) - r += format_line("config", "depth", str(self.depth)) - r += format_line("config", "with_rle", str(int(self.with_rle))) - - for e in layout: - r += format_line("layout", ns.get_name(e), str(flen(e))) - write_to_file(filename, r) diff --git a/miscope/std.py b/miscope/std.py deleted file mode 100644 index d7ffc7e3..00000000 --- a/miscope/std.py +++ /dev/null @@ -1,13 +0,0 @@ -from migen.genlib.record import * - -def dat_layout(dw): - return [ - ("stb", 1, DIR_M_TO_S), - ("dat", dw, DIR_M_TO_S) - ] - -def hit_layout(): - return [ - ("stb", 1, DIR_M_TO_S), - ("hit", 1, DIR_M_TO_S) - ] diff --git a/miscope/storage.py b/miscope/storage.py deleted file mode 100644 index 31797045..00000000 --- a/miscope/storage.py +++ /dev/null @@ -1,114 +0,0 @@ -from migen.fhdl.std import * -from migen.bank.description import * -from migen.genlib.fifo import SyncFIFOBuffered as SyncFIFO -from migen.genlib.fsm import FSM, NextState -from migen.genlib.record import * - -from miscope.std import * - -class RunLengthEncoder(Module, AutoCSR): - def __init__(self, width, length=1024): - self.width = width - self.length = length - - self.sink = Record(dat_layout(width)) - self.source = Record(dat_layout(width)) - - self._enable = CSRStorage() - - ### - - enable = self._enable.storage - - sink_d = Record(dat_layout(width)) - self.sync += If(self.sink.stb, sink_d.eq(self.sink)) - - cnt = Signal(max=length) - cnt_inc = Signal() - cnt_reset = Signal() - cnt_max = Signal() - - self.sync += \ - If(cnt_reset, - cnt.eq(1), - ).Elif(cnt_inc, - cnt.eq(cnt+1) - ) - self.comb += cnt_max.eq(cnt == length) - - change = Signal() - self.comb += change.eq(self.sink.stb & (self.sink.dat != sink_d.dat)) - - fsm = FSM(reset_state="BYPASS") - self.submodules += fsm - - fsm.act("BYPASS", - sink_d.connect(self.source), - cnt_reset.eq(1), - If(enable & ~change & self.sink.stb, NextState("COUNT")) - ) - - fsm.act("COUNT", - cnt_inc.eq(self.sink.stb), - If(change | cnt_max | ~enable, - self.source.stb.eq(1), - self.source.dat[width-1].eq(1), # Set RLE bit - self.source.dat[:flen(cnt)].eq(cnt), - NextState("BYPASS") - ) - ), - -class Recorder(Module, AutoCSR): - def __init__(self, width, depth): - self.width = width - - self.trig_sink = Record(hit_layout()) - self.dat_sink = Record(dat_layout(width)) - - self._trigger = CSR() - self._length = CSRStorage(bits_for(depth)) - self._offset = CSRStorage(bits_for(depth)) - self._done = CSRStatus() - - self._read_en = CSR() - self._read_empty = CSRStatus() - self._read_dat = CSRStatus(width) - - ### - - fifo = InsertReset(SyncFIFO(width, depth)) - self.submodules += fifo - - fsm = FSM(reset_state="IDLE") - self.submodules += fsm - - - self.comb += [ - self._read_empty.status.eq(~fifo.readable), - self._read_dat.status.eq(fifo.dout), - ] - - fsm.act("IDLE", - If(self._trigger.re & self._trigger.r, - NextState("PRE_HIT_RECORDING"), - fifo.reset.eq(1), - ), - fifo.re.eq(self._read_en.re & self._read_en.r), - self._done.status.eq(1) - ) - - fsm.act("PRE_HIT_RECORDING", - fifo.we.eq(self.dat_sink.stb), - fifo.din.eq(self.dat_sink.dat), - - fifo.re.eq(fifo.level >= self._offset.storage), - - If(self.trig_sink.stb & self.trig_sink.hit, NextState("POST_HIT_RECORDING")) - ) - - fsm.act("POST_HIT_RECORDING", - fifo.we.eq(self.dat_sink.stb), - fifo.din.eq(self.dat_sink.dat), - - If(~fifo.writable | (fifo.level >= self._length.storage), NextState("IDLE")) - ) diff --git a/miscope/trigger.py b/miscope/trigger.py deleted file mode 100644 index 6e795f52..00000000 --- a/miscope/trigger.py +++ /dev/null @@ -1,140 +0,0 @@ -from migen.fhdl.std import * -from migen.fhdl.specials import Memory -from migen.bank.description import * -from migen.genlib.record import * - -from miscope.std import * - -class Term(Module, AutoCSR): - def __init__(self, width): - self.width = width - - self.sink = Record(dat_layout(width)) - self.source = Record(hit_layout()) - - self._trig = CSRStorage(width) - self._mask = CSRStorage(width) - - ### - - trig = self._trig.storage - mask = self._mask.storage - dat = self.sink.dat - hit = self.source.hit - - self.comb += [ - hit.eq((dat & mask) == trig), - self.source.stb.eq(self.sink.stb) - ] - -class RangeDetector(Module, AutoCSR): - def __init__(self, width): - self.width = width - - self.sink = Record(dat_layout(width)) - self.source = Record(hit_layout()) - - self._low = CSRStorage(width) - self._high = CSRStorage(width) - - ### - - low = self._low.storage - high = self._high.storage - dat = self.sink.dat - hit = self.source.hit - - self.comb += [ - hit.eq((dat >= low) & (dat <= high)), - self.source.stb.eq(self.sink.stb) - ] - -class EdgeDetector(Module, AutoCSR): - def __init__(self, width): - self.width = width - - self.sink = Record(dat_layout(width)) - self.source = Record(hit_layout()) - - self._rising_mask = CSRStorage(width) - self._falling_mask = CSRStorage(width) - self._both_mask = CSRStorage(width) - - ### - - rising_mask = self._rising_mask.storage - falling_mask = self._falling_mask.storage - both_mask = self._both_mask.storage - - dat = self.sink.dat - dat_d = Signal(width) - rising_hit = Signal() - falling_hit = Signal() - both_hit = Signal() - hit = self.source.hit - - self.sync += dat_d.eq(dat) - - self.comb += [ - rising_hit.eq(rising_mask & dat & ~dat_d), - falling_hit.eq(rising_mask & ~dat & dat_d), - both_hit.eq((both_mask & dat) != (both_mask & dat_d)), - hit.eq(rising_hit | falling_hit | both_hit), - self.source.stb.eq(self.sink.stb) - ] - -class Sum(Module, AutoCSR): - def __init__(self, ports=4): - - self.sinks = [Record(hit_layout()) for p in range(ports)] - self.source = Record(hit_layout()) - - self._prog_we = CSRStorage() - self._prog_adr = CSRStorage(ports) #FIXME - self._prog_dat = CSRStorage() - - mem = Memory(1, 2**ports) - lut_port = mem.get_port() - prog_port = mem.get_port(write_capable=True) - - self.specials += mem, lut_port, prog_port - - ### - - # Lut prog - self.comb += [ - prog_port.we.eq(self._prog_we.storage), - prog_port.adr.eq(self._prog_adr.storage), - prog_port.dat_w.eq(self._prog_dat.storage) - ] - - # Lut read - for i, sink in enumerate(self.sinks): - self.comb += lut_port.adr[i].eq(sink.hit) - - # Drive source - self.comb += [ - self.source.stb.eq(optree("&", [sink.stb for sink in self.sinks])), - self.source.hit.eq(lut_port.dat_r), - ] - - -class Trigger(Module, AutoCSR): - def __init__(self, width, ports): - self.width = width - self.ports = ports - - self.submodules.sum = Sum(len(ports)) - for i, port in enumerate(ports): - setattr(self.submodules, "port"+str(i), port) - - self.sink = Record(dat_layout(width)) - self.source = self.sum.source - - ### - - for i, port in enumerate(ports): - self.comb += [ - self.sink.connect(port.sink), - port.source.connect(self.sum.sinks[i]) - ] diff --git a/miscope/uart2wishbone.py b/miscope/uart2wishbone.py deleted file mode 100644 index e8e773f6..00000000 --- a/miscope/uart2wishbone.py +++ /dev/null @@ -1,219 +0,0 @@ -from migen.fhdl.std import * -from migen.genlib.record import * -from migen.genlib.fsm import FSM, NextState -from migen.genlib.misc import chooser -from migen.bank.description import * -from migen.bus import wishbone - -from misoclib.uart import UARTRX, UARTTX - -@DecorateModule(InsertReset) -@DecorateModule(InsertCE) -class Counter(Module): - def __init__(self, signal=None, **kwargs): - if signal is None: - self.value = Signal(**kwargs) - else: - self.value = signal - self.width = flen(self.value) - self.sync += self.value.eq(self.value+1) - -@DecorateModule(InsertReset) -@DecorateModule(InsertCE) -class Timeout(Module): - def __init__(self, length): - self.reached = Signal() - ### - value = Signal(max=length) - self.sync += value.eq(value+1) - self.comb += [ - self.reached.eq(value == length) - ] - -class UART(Module, AutoCSR): - def __init__(self, pads, clk_freq, baud=115200): - self._tuning_word = CSRStorage(32, reset=int((baud/clk_freq)*2**32)) - tuning_word = self._tuning_word.storage - - ### - - self.rx = UARTRX(pads, tuning_word) - self.tx = UARTTX(pads, tuning_word) - self.submodules += self.rx, self.tx - -class UARTPads: - def __init__(self): - self.rx = Signal() - self.tx = Signal() - -class UARTMux(Module): - def __init__(self, pads): - self.sel = Signal(max=2) - self.shared_pads = UARTPads() - self.bridge_pads = UARTPads() - - ### - # Route rx pad: - # when sel==0, route it to shared rx and bridge rx - # when sel==1, route it only to bridge rx - self.comb += \ - If(self.sel==0, - self.shared_pads.rx.eq(pads.rx), - self.bridge_pads.rx.eq(pads.rx) - ).Else( - self.bridge_pads.rx.eq(pads.rx) - ) - - # Route tx: - # when sel==0, route shared tx to pads tx - # when sel==1, route bridge tx to pads tx - self.comb += \ - If(self.sel==0, - pads.tx.eq(self.shared_pads.tx) - ).Else( - pads.tx.eq(self.bridge_pads.tx) - ) - -class UART2Wishbone(Module, AutoCSR): - cmds = { - "write" : 0x01, - "read" : 0x02 - } - def __init__(self, pads, clk_freq, baud=115200, share_uart=False): - self.wishbone = wishbone.Interface() - if share_uart: - self._sel = CSRStorage() - ### - if share_uart: - self.uart_mux = UARTMux(pads) - uart = UART(self.uart_mux.bridge_pads, clk_freq, baud) - self.shared_pads = self.uart_mux.shared_pads - self.comb += self.uart_mux.sel.eq(self._sel.storage) - else: - uart = UART(pads, clk_freq, baud) - self.submodules += uart - - byte_counter = Counter(bits_sign=3) - word_counter = Counter(bits_sign=8) - self.submodules += byte_counter, word_counter - - - cmd = Signal(8) - cmd_ce = Signal() - - length = Signal(8) - length_ce = Signal() - - address = Signal(32) - address_ce = Signal() - - data = Signal(32) - rx_data_ce = Signal() - tx_data_ce = Signal() - - self.sync += [ - If(cmd_ce, cmd.eq(uart.rx.source.d)), - If(length_ce, length.eq(uart.rx.source.d)), - If(address_ce, address.eq(Cat(uart.rx.source.d, address[0:24]))), - If(rx_data_ce, - data.eq(Cat(uart.rx.source.d, data[0:24])) - ).Elif(tx_data_ce, - data.eq(self.wishbone.dat_r) - ) - ] - - ### - fsm = InsertReset(FSM(reset_state="IDLE")) - timeout = Timeout(clk_freq//10) - self.submodules += fsm, timeout - - self.comb += [ - timeout.ce.eq(1), - fsm.reset.eq(timeout.reached) - ] - fsm.act("IDLE", - timeout.reset.eq(1), - If(uart.rx.source.stb, - cmd_ce.eq(1), - If( (uart.rx.source.d == self.cmds["write"]) | - (uart.rx.source.d == self.cmds["read"]), - NextState("RECEIVE_LENGTH") - ), - byte_counter.reset.eq(1), - word_counter.reset.eq(1) - ) - ) - fsm.act("RECEIVE_LENGTH", - If(uart.rx.source.stb, - length_ce.eq(1), - NextState("RECEIVE_ADDRESS") - ) - ) - fsm.act("RECEIVE_ADDRESS", - If(uart.rx.source.stb, - address_ce.eq(1), - byte_counter.ce.eq(1), - If(byte_counter.value == 3, - If(cmd == self.cmds["write"], - NextState("RECEIVE_DATA") - ).Elif(cmd == self.cmds["read"], - NextState("READ_DATA") - ), - byte_counter.reset.eq(1), - ) - ) - ) - fsm.act("RECEIVE_DATA", - If(uart.rx.source.stb, - rx_data_ce.eq(1), - byte_counter.ce.eq(1), - If(byte_counter.value == 3, - NextState("WRITE_DATA"), - byte_counter.reset.eq(1) - ) - ) - ) - self.comb += [ - self.wishbone.adr.eq(address + word_counter.value), - self.wishbone.dat_w.eq(data), - self.wishbone.sel.eq(2**flen(self.wishbone.sel)-1) - ] - fsm.act("WRITE_DATA", - self.wishbone.stb.eq(1), - self.wishbone.we.eq(1), - self.wishbone.cyc.eq(1), - If(self.wishbone.ack, - word_counter.ce.eq(1), - If(word_counter.value == (length-1), - NextState("IDLE") - ).Else( - NextState("RECEIVE_DATA") - ) - ) - ) - fsm.act("READ_DATA", - self.wishbone.stb.eq(1), - self.wishbone.we.eq(0), - self.wishbone.cyc.eq(1), - If(self.wishbone.ack, - tx_data_ce.eq(1), - NextState("SEND_DATA") - ) - ) - self.comb += \ - chooser(data, byte_counter.value, uart.tx.sink.d, n=4, reverse=True) - fsm.act("SEND_DATA", - uart.tx.sink.stb.eq(1), - If(uart.tx.sink.ack, - byte_counter.ce.eq(1), - If(byte_counter.value == 3, - word_counter.ce.eq(1), - If(word_counter.value == (length-1), - NextState("IDLE") - ).Else( - NextState("READ_DATA"), - byte_counter.reset.eq(1) - ) - ) - ) - ) diff --git a/setup.py b/setup.py index 740be67b..979c1655 100644 --- a/setup.py +++ b/setup.py @@ -9,18 +9,18 @@ README = open(os.path.join(here, "README")).read() required_version = (3, 3) if sys.version_info < required_version: - raise SystemExit("Migscope requires python {0} or greater".format( + raise SystemExit("LiteScope requires python {0} or greater".format( ".".join(map(str, required_version)))) setup( - name="miscope", + name="litescope", version="unknown", - description="Migen based Fpga logic analyzer", + description="small footprint and configurable embedded FPGA logic analyzer", long_description=README, author="Florent Kermarrec", author_email="florent@enjoy-digital.fr", url="http://enjoy-digital.fr", - download_url="https://github.com/Florent-Kermarrec/miscope", + download_url="https://github.com/Florent-Kermarrec/litescope", packages=find_packages(here), license="GPL", platforms=["Any"], diff --git a/sim/cpuif.py b/sim/cpuif.py deleted file mode 100644 index 6a0ed40b..00000000 --- a/sim/cpuif.py +++ /dev/null @@ -1,11 +0,0 @@ -from migen.bank.description import CSRStatus - -def get_csr_csv(csr_base, bank_array): - r = "" - for name, csrs, mapaddr, rmap in bank_array.banks: - reg_base = csr_base + 0x800*mapaddr - for csr in csrs: - nr = (csr.size + 7)//8 - r += "{}_{},0x{:08x},{},{}\n".format(name, csr.name, reg_base, nr, "ro" if isinstance(csr, CSRStatus) else "rw") - reg_base += 4*nr - return r diff --git a/sim/tb_recorder_csr.py b/sim/tb_recorder_csr.py deleted file mode 100644 index 368bb800..00000000 --- a/sim/tb_recorder_csr.py +++ /dev/null @@ -1,134 +0,0 @@ -from migen.fhdl.std import * -from migen.fhdl import verilog -from migen.bus import csr -from migen.sim.generic import run_simulation -from migen.bus.transactions import * - -from miscope.std import * -from miscope.storage import * - -from mibuild.tools import write_to_file -from miscope.tools.regs import * -from miscope.tools.truthtable import * - -from cpuif import * - -class Csr2Trans(): - def __init__(self): - self.t = [] - - def write_csr(self, adr, value): - self.t.append(TWrite(adr//4, value)) - - def read_csr(self, adr): - self.t.append(TRead(adr//4)) - return 0 - - -triggered = False -dat = 0 - -rec_done = False - -dat_rdy = False - -rec_length = 128 - -def csr_configure(bus, regs): - # Length - regs.recorder_length.write(rec_length) - - # Offset - regs.recorder_offset.write(0) - - # Trigger - regs.recorder_trigger.write(1) - - return bus.t - -def csr_read_data(bus, regs): - for i in range(rec_length+100): - regs.recorder_read_dat.read() - regs.recorder_read_en.write(1) - return bus.t - -def csr_transactions(bus, regs): - for t in csr_configure(bus, regs): - yield t - - for t in range(100): - yield None - - global triggered - triggered = True - - for t in range(512): - yield None - - for t in csr_read_data(bus, regs): - yield t - - for t in range(100): - yield None - - -class TB(Module): - csr_base = 0 - csr_map = { - "recorder": 1, - } - def __init__(self, addrmap=None): - self.csr_base = 0 - - # Recorder - self.submodules.recorder = Recorder(32, 1024) - - # Csr - self.submodules.csrbankarray = csrgen.BankArray(self, - lambda name, memory: self.csr_map[name if memory is None else name + "_" + memory.name_override]) - - # Csr Master - csr_header = get_csr_csv(self.csr_base, self.csrbankarray) - write_to_file("csr.csv", csr_header) - - bus = Csr2Trans() - regs = build_map(addrmap, bus.read_csr, bus.write_csr) - self.submodules.master = csr.Initiator(csr_transactions(bus, regs)) - - self.submodules.csrcon = csr.Interconnect(self.master.bus, self.csrbankarray.get_buses()) - - # Recorder Data - def recorder_data(self, selfp): - selfp.recorder.dat_sink.stb = 1 - if not hasattr(self, "cnt"): - self.cnt = 0 - self.cnt += 1 - - selfp.recorder.dat_sink.dat = self.cnt - - global triggered - if triggered: - selfp.recorder.trig_sink.stb = 1 - selfp.recorder.trig_sink.hit = 1 - triggered = False - else: - selfp.recorder.trig_sink.stb = 0 - selfp.recorder.trig_sink.hit = 0 - - # Simulation - def end_simulation(self, selfp): - if self.master.done: - raise StopSimulation - - def do_simulation(self, selfp): - self.recorder_data(selfp) - self.end_simulation(selfp) - - -def main(): - tb = TB(addrmap="csr.csv") - run_simulation(tb, ncycles=2000, vcd_name="tb_recorder_csr.vcd") - print("Sim Done") - input() - -main() \ No newline at end of file diff --git a/sim/tb_rle.py b/sim/tb_rle.py deleted file mode 100644 index 1dd8e75c..00000000 --- a/sim/tb_rle.py +++ /dev/null @@ -1,45 +0,0 @@ -from migen.fhdl.std import * -from migen.sim.generic import run_simulation - -from miscope import storage - -rle_test_seq = iter( - [ 0x00AA, - 0x00AB, - 0x00AC, - 0x00AC, - 0x00AC, - 0x00AC, - 0x00AD, - 0x00AE, - 0x00AE, - 0x00AE, - 0x00AE, - 0x00AE, - 0x00AE, - 0x00AE, - 0x00AE - ]*10 -) - -class TB(Module): - def __init__(self): - - # Rle - self.submodules.rle = storage.RunLengthEncoder(16, 32) - - def do_simulation(self, selfp): - selfp.rle._r_enable.storage = 1 - selfp.rle.sink.stb = 1 - try: - selfp.rle.sink.dat = next(rle_test_seq) - except: - pass - -def main(): - tb = TB() - run_simulation(tb, ncycles=8000, vcd_name="tb_rle.vcd") - print("Sim Done") - input() - -main() diff --git a/sim/tb_trigger_csr.py b/sim/tb_trigger_csr.py deleted file mode 100644 index ef74703b..00000000 --- a/sim/tb_trigger_csr.py +++ /dev/null @@ -1,102 +0,0 @@ -from migen.fhdl.std import * -from migen.fhdl import verilog -from migen.bus import csr -from migen.sim.generic import run_simulation -from migen.bus.transactions import * - -from miscope.std import * -from miscope.trigger import * - -from mibuild.tools import write_to_file -from miscope.tools.regs import * -from miscope.tools.truthtable import * - -from cpuif import * - -class Csr2Trans(): - def __init__(self): - self.t = [] - - def write_csr(self, adr, value): - self.t.append(TWrite(adr//4, value)) - - def read_csr(self, adr): - self.t.append(TRead(adr//4)) - -def csr_prog_mila(bus, regs): - regs.trigger_port0_mask.write(0xFFFFFFFF) - regs.trigger_port0_trig.write(0xDEADBEEF) - regs.trigger_port1_mask.write(0xFFFFFFFF) - regs.trigger_port1_trig.write(0xCAFEFADE) - regs.trigger_port2_mask.write(0xFFFFFFFF) - regs.trigger_port2_trig.write(0xDEADBEEF) - regs.trigger_port3_mask.write(0xFFFFFFFF) - regs.trigger_port3_trig.write(0xCAFEFADE) - - sum_tt = gen_truth_table("i1 & i2 & i3 & i4") - sum_trans = [] - for i in range(len(sum_tt)): - regs.trigger_sum_prog_adr.write(i) - regs.trigger_sum_prog_dat.write(sum_tt[i]) - regs.trigger_sum_prog_we.write(1) - - return bus.t - - -csr_done = False - -def csr_transactions(bus, regs): - for t in csr_prog_mila(bus, regs): - yield t - global csr_done - csr_done = True - for t in range(100): - yield None - -class TB(Module): - csr_base = 0 - csr_map = { - "trigger": 1, - } - def __init__(self, addrmap=None): - self.csr_base = 0 - - # Trigger - term0 = Term(32) - term1 = Term(32) - term2 = Term(32) - term3 = Term(32) - self.submodules.trigger = Trigger(32, [term0, term1, term2, term3]) - - # Csr - self.submodules.csrbankarray = csrgen.BankArray(self, - lambda name, memory: self.csr_map[name if memory is None else name + "_" + memory.name_override]) - - # Csr Master - csr_header = get_csr_csv(self.csr_base, self.csrbankarray) - write_to_file("csr.csv", csr_header) - - bus = Csr2Trans() - regs = build_map(addrmap, bus.read_csr, bus.write_csr) - self.submodules.master = csr.Initiator(csr_transactions(bus, regs)) - - self.submodules.csrcon = csr.Interconnect(self.master.bus, self.csrbankarray.get_buses()) - - self.terms = [term0, term1, term2, term3] - - def do_simulation(self, selfp): - for term in selfp.terms: - term.sink.stb = 1 - if csr_done: - selfp.terms[0].sink.dat = 0xDEADBEEF - selfp.terms[1].sink.dat = 0xCAFEFADE - selfp.terms[2].sink.dat = 0xDEADBEEF - selfp.terms[3].sink.dat = 0xCAFEFADE - -def main(): - tb = TB(addrmap="csr.csv") - run_simulation(tb, ncycles=2000, vcd_name="tb_trigger_csr.vcd") - print("Sim Done") - input() - -main() diff --git a/targets/__init__.py b/targets/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/targets/simple.py b/targets/simple.py new file mode 100644 index 00000000..40c9993c --- /dev/null +++ b/targets/simple.py @@ -0,0 +1,83 @@ +import os + +from migen.bank import csrgen +from migen.bus import wishbone, csr +from migen.bus import wishbone2csr +from migen.genlib.cdc import * +from migen.genlib.resetsync import AsyncResetSynchronizer +from migen.bank.description import * + +from misoclib import identifier + +from litescope.common import * +from litescope.bridge.uart2wb import LiteScopeUART2WB + +class _CRG(Module): + def __init__(self, clk_in): + self.clock_domains.cd_sys = ClockDomain() + self.clock_domains.cd_por = ClockDomain(reset_less=True) + + # Power on Reset (vendor agnostic) + rst_n = Signal() + self.sync.por += rst_n.eq(1) + self.comb += [ + self.cd_sys.clk.eq(clk_in), + self.cd_por.clk.eq(clk_in), + self.cd_sys.rst.eq(~rst_n) + ] + +class GenSoC(Module): + csr_base = 0x00000000 + csr_data_width = 32 + csr_map = { + "bridge": 0, + "identifier": 1, + } + interrupt_map = {} + cpu_type = None + def __init__(self, platform, clk_freq): + self.clk_freq = clk_freq + # UART <--> Wishbone bridge + self.submodules.uart2wb = LiteScopeUART2WB(platform.request("serial"), clk_freq, baud=115200) + + # CSR bridge 0x00000000 (shadow @0x00000000) + self.submodules.wishbone2csr = wishbone2csr.WB2CSR(bus_csr=csr.Interface(self.csr_data_width)) + self._wb_masters = [self.uart2wb.wishbone] + self._wb_slaves = [(lambda a: a[23:25] == 0, self.wishbone2csr.wishbone)] + self.cpu_csr_regions = [] # list of (name, origin, busword, csr_list/Memory) + + # CSR + self.submodules.identifier = identifier.Identifier(0, int(clk_freq), 0) + + def add_cpu_memory_region(self, name, origin, length): + self.cpu_memory_regions.append((name, origin, length)) + + def add_cpu_csr_region(self, name, origin, busword, obj): + self.cpu_csr_regions.append((name, origin, busword, obj)) + + def do_finalize(self): + # Wishbone + self.submodules.wishbonecon = wishbone.InterconnectShared(self._wb_masters, + self._wb_slaves, register=True) + + # CSR + self.submodules.csrbankarray = csrgen.BankArray(self, + lambda name, memory: self.csr_map[name if memory is None else name + "_" + memory.name_override], + data_width=self.csr_data_width) + self.submodules.csrcon = csr.Interconnect(self.wishbone2csr.csr, self.csrbankarray.get_buses()) + for name, csrs, mapaddr, rmap in self.csrbankarray.banks: + self.add_cpu_csr_region(name, 0xe0000000+0x800*mapaddr, flen(rmap.bus.dat_w), csrs) + for name, memory, mapaddr, mmap in self.csrbankarray.srams: + self.add_cpu_csr_region(name, 0xe0000000+0x800*mapaddr, flen(rmap.bus.dat_w), memory) + +class LiteScopeSoC(GenSoC, AutoCSR): + default_platform = "de0nano" + csr_map = {} + csr_map.update(GenSoC.csr_map) + + def __init__(self, platform, export_mila=False): + clk_freq = 50*1000000 + GenSoC.__init__(self, platform, clk_freq) + self.submodules.crg = _CRG(platform.request("clk50")) + +default_subtarget = LiteScopeSoC diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 00000000..6d23bdcc --- /dev/null +++ b/test/Makefile @@ -0,0 +1,7 @@ +LSDIR = ../ +PYTHON = python3 + +CMD = PYTHONPATH=$(LSDIR) $(PYTHON) + +test_regs: + $(CMD) test_regs.py diff --git a/test/config.py b/test/config.py new file mode 100644 index 00000000..7fc57068 --- /dev/null +++ b/test/config.py @@ -0,0 +1,9 @@ +from litescope.host.driver import LiteScopeUART2WBDriver + +csr_csv_file = "./csr.csv" +busword = 32 +debug_wb = False + +com = 3 +baud = 115200 +wb = LiteScopeUART2WBDriver(com, baud, csr_csv_file, busword, debug_wb) \ No newline at end of file diff --git a/test/test_regs.py b/test/test_regs.py new file mode 100644 index 00000000..bb59b641 --- /dev/null +++ b/test/test_regs.py @@ -0,0 +1,11 @@ +from config import * + +wb.open() +regs = wb.regs +### +print("sysid : 0x%04x" %regs.identifier_sysid.read()) +print("revision : 0x%04x" %regs.identifier_revision.read()) +print("frequency : %d MHz" %(regs.identifier_frequency.read()/1000000)) +print("l2_size : %d" %regs.identifier_l2_size.read()) +### +wb.close()