diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2d0ad6a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +indent_size = 4 +max_line_length = 100 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/README.md b/README.md index 56866fa..74493c9 100644 --- a/README.md +++ b/README.md @@ -5,5 +5,5 @@ A small framework for building projects with [Amaranth]. See the [template project] for usage. [Amaranth]: https://amaranth-lang.org/ -[template project]: https://github.com/kivikakk/niar-template +[template project]: https://github.com/kivikakk/niar/tree/main/template diff --git a/template/.editorconfig b/template/.editorconfig new file mode 100644 index 0000000..2d0ad6a --- /dev/null +++ b/template/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +indent_size = 4 +max_line_length = 100 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/template/.gitignore b/template/.gitignore new file mode 100644 index 0000000..933d84d --- /dev/null +++ b/template/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +/build +*.vcd diff --git a/template/cxxrtl/main.cc b/template/cxxrtl/main.cc new file mode 100644 index 0000000..43b8d07 --- /dev/null +++ b/template/cxxrtl/main.cc @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +#include +#include + +static cxxrtl_design::p_newproject top; +static cxxrtl::vcd_writer vcd; +static uint64_t vcd_time = 0; + +static void step() { + top.p_clk.set(true); + top.step(); + vcd.sample(vcd_time++); + top.p_clk.set(false); + top.step(); + vcd.sample(vcd_time++); +} + +int main(int argc, char **argv) { + std::optional vcd_out = std::nullopt; + + for (int i = 1; i < argc; ++i) { + if (strcmp(argv[i], "--vcd") == 0 && argc >= (i + 2)) { + vcd_out = std::string(argv[++i]); + } else { + std::cerr << "unknown argument \"" << argv[i] << "\"" << std::endl; + return 2; + } + } + + if (vcd_out.has_value()) { + debug_items di; + top.debug_info(&di, nullptr, "top "); + vcd.add(di); + } + + top.p_rst.set(true); + step(); + + top.p_rst.set(false); + + // ledr should be low or high according to 'expected', where each element + // represents 1/4th of a second. ledg should always be high. + // + // This mirrors TestTop in Python. + int rc = 0; + bool done = false; + + std::vector expected = {0, 1, 1, 0, 0, 1, 1, 0}; + for (std::vector::size_type i = 0; i < expected.size() && !done; ++i) { + for (int j = 0; j < (CLOCK_HZ / 4); ++j) { + if (top.p_ledr.get() != expected[i]) { + std::cerr << "unexpected ledr at i(" << i << "), j(" << j << ")" + << std::endl; + rc = 1; + done = true; + break; + } + assert(top.p_ledg); + + step(); + } + } + + std::cout << "finished on cycle " << (vcd_time >> 1) << std::endl; + + if (vcd_out.has_value()) { + std::ofstream of(*vcd_out); + of << vcd.buffer; + } + + return rc; +} diff --git a/template/newproject/__init__.py b/template/newproject/__init__.py new file mode 100644 index 0000000..d1a833c --- /dev/null +++ b/template/newproject/__init__.py @@ -0,0 +1,13 @@ +import niar + +from . import rtl +from .targets import cxxrtl, icebreaker, ulx3s + +__all__ = ["NewProject"] + + +class NewProject(niar.Project): + name = "newproject" + top = rtl.Top + targets = [icebreaker, ulx3s] + cxxrtl_targets = [cxxrtl] diff --git a/template/newproject/__main__.py b/template/newproject/__main__.py new file mode 100644 index 0000000..88ac09f --- /dev/null +++ b/template/newproject/__main__.py @@ -0,0 +1,3 @@ +from . import NewProject + +NewProject().main() diff --git a/template/newproject/rtl/__init__.py b/template/newproject/rtl/__init__.py new file mode 100644 index 0000000..7336a57 --- /dev/null +++ b/template/newproject/rtl/__init__.py @@ -0,0 +1,65 @@ +from amaranth import Module, Signal +from amaranth.lib import wiring +from amaranth.lib.wiring import Out + +from ..targets import cxxrtl, icebreaker, ulx3s + +__all__ = ["Top"] + + +class Top(wiring.Component): + def __init__(self, platform): + if isinstance(platform, cxxrtl): + super().__init__( + { + "ledr": Out(1), + "ledg": Out(1), + } + ) + else: + super().__init__({}) + + def elaborate(self, platform): + + m = Module() + + m.submodules.blinker = blinker = Blinker() + + match platform: + case icebreaker(): + m.d.comb += platform.request("led_r").o.eq(blinker.ledr) + m.d.comb += platform.request("led_g").o.eq(blinker.ledg) + + case ulx3s(): + m.d.comb += platform.request("led", 0).o.eq(blinker.ledr) + m.d.comb += platform.request("led", 1).o.eq(blinker.ledg) + + case cxxrtl(): + m.d.comb += self.ledr.eq(blinker.ledr) + m.d.comb += self.ledg.eq(blinker.ledg) + + return m + + +class Blinker(wiring.Component): + ledr: Out(1) + ledg: Out(1) + + def elaborate(self, platform): + m = Module() + + m.d.comb += self.ledg.eq(1) + + timer_top = (int(platform.default_clk_frequency) // 2) - 1 + timer_half = (int(platform.default_clk_frequency) // 4) - 1 + timer_reg = Signal(range(timer_top), init=timer_half) + + with m.If(timer_reg == 0): + m.d.sync += [ + self.ledr.eq(~self.ledr), + timer_reg.eq(timer_top), + ] + with m.Else(): + m.d.sync += timer_reg.eq(timer_reg - 1) + + return m diff --git a/template/newproject/targets.py b/template/newproject/targets.py new file mode 100644 index 0000000..25d3ca4 --- /dev/null +++ b/template/newproject/targets.py @@ -0,0 +1,17 @@ +import niar +from amaranth_boards.icebreaker import ICEBreakerPlatform +from amaranth_boards.ulx3s import ULX3S_45F_Platform + +__all__ = ["icebreaker", "ulx3s", "cxxrtl"] + + +class icebreaker(ICEBreakerPlatform): + pass + + +class ulx3s(ULX3S_45F_Platform): + pass + + +class cxxrtl(niar.CxxrtlPlatform): + default_clk_frequency = 3_000_000.0 diff --git a/template/pyproject.toml b/template/pyproject.toml new file mode 100644 index 0000000..2abd799 --- /dev/null +++ b/template/pyproject.toml @@ -0,0 +1,18 @@ +[project] +name = "newproject" +version = "0.0" +description = "" +authors = [ + {name = "name", email = "email@example.com"}, +] +dependencies = [ + "amaranth >= 0.5, < 0.7", + "amaranth-boards", + "niar >= 0.1", +] +requires-python = ">=3.8" +license = {text = "BSD-2-Clause"} + +[build-system] +requires = ["pdm-backend"] +build-backend = "pdm.backend" diff --git a/template/tests/__init__.py b/template/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/template/tests/test_top.py b/template/tests/test_top.py new file mode 100644 index 0000000..d1b1e4e --- /dev/null +++ b/template/tests/test_top.py @@ -0,0 +1,30 @@ +import unittest + +from amaranth.hdl import Fragment +from amaranth.sim import Simulator + +from newproject.rtl import Blinker + + +class test: + simulation = True + default_clk_frequency = 8.0 + + +class TestBlinker(unittest.TestCase): + platform = test() + + def test_blinks(self): + dut = Blinker() + + async def testbench(ctx): + for ledr in [0, 1, 1, 0, 0, 1, 1, 0]: + for _ in range(2): + assert ctx.get(dut.ledr) == ledr + assert ctx.get(dut.ledg) + await ctx.tick() + + sim = Simulator(Fragment.get(dut, self.platform)) + sim.add_clock(1 / 8) + sim.add_testbench(testbench) + sim.run()