diff --git a/default.nix b/default.nix index 4ec561a..159b096 100644 --- a/default.nix +++ b/default.nix @@ -3,6 +3,8 @@ rec { vivado = import ./eda/vivado.nix { inherit pkgs; }; nmigen = pkgs.callPackage ./eda/nmigen.nix { }; nmigen-boards = pkgs.callPackage ./eda/nmigen-boards.nix { inherit nmigen; }; + nmigen-stdio = pkgs.callPackage ./eda/nmigen-stdio.nix { inherit nmigen; }; + nmigen-soc = pkgs.callPackage ./eda/nmigen-soc.nix { inherit nmigen; }; scala-spinalhdl = pkgs.callPackage ./eda/scala-spinalhdl.nix {}; jtagtap = pkgs.callPackage ./cores/jtagtap.nix { inherit nmigen; }; @@ -15,7 +17,7 @@ rec { litex = pkgs.callPackage ./cores/litex.nix { inherit nmigen; }; litedram = pkgs.callPackage ./cores/litedram.nix { inherit litex; }; - heavycomps = pkgs.callPackage ./heavycomps.nix { inherit nmigen; }; + heavycomps = pkgs.callPackage ./heavycomps.nix { inherit nmigen; inherit nmigen-stdio; inherit nmigen-soc; }; binutils-riscv32 = pkgs.callPackage ./compilers/binutils.nix { platform = "riscv32"; }; gcc-riscv32 = pkgs.callPackage ./compilers/gcc.nix { platform = "riscv32"; platform-binutils = binutils-riscv32; }; @@ -23,5 +25,5 @@ rec { gcc-riscv64 = pkgs.callPackage ./compilers/gcc.nix { platform = "riscv64"; platform-binutils = binutils-riscv64; }; rust-riscv32i-crates = pkgs.callPackage ./compilers/rust-riscv32i-crates.nix { }; - fw-helloworld = pkgs.callPackage ./firmware { inherit rust-riscv32i-crates binutils-riscv32; }; + fw-helloworld = pkgs.callPackage ./firmware/testing { inherit rust-riscv32i-crates binutils-riscv32; }; } diff --git a/eda/nmigen-boards.nix b/eda/nmigen-boards.nix index 6dae87d..f21ee4e 100644 --- a/eda/nmigen-boards.nix +++ b/eda/nmigen-boards.nix @@ -5,9 +5,16 @@ python3Packages.buildPythonPackage { version = "2019-10-13"; src = fetchgit { - url = "https://github.com/m-labs/nmigen-boards"; - rev = "bc074915d6c67a33447d4730b720b37e3ea4bb3f"; - sha256 = "1qp32783p0bg6fxfj0wd7fd3if8az7w1r521wcbznxakdrqhrqjd"; + # Using official master branch + #url = "https://github.com/m-labs/nmigen-boards"; + #rev = "bc074915d6c67a33447d4730b720b37e3ea4bb3f"; + #sha256 = "1qp32783p0bg6fxfj0wd7fd3if8az7w1r521wcbznxakdrqhrqjd"; + + # Using harry's fork + url = "https://github.com/HarryHo90sHK/nmigen-boards"; + rev = "d51e1d5f53aca59d138860d332edf84fdce7cfa3"; + sha256 = "0yfs81bhcx4pm4q83pjhiibc1zknsllimqb2c753r3vvbcg4frmw"; + leaveDotGit = true; }; diff --git a/eda/nmigen-soc.nix b/eda/nmigen-soc.nix new file mode 100644 index 0000000..8c61e16 --- /dev/null +++ b/eda/nmigen-soc.nix @@ -0,0 +1,25 @@ +{ stdenv, fetchgit, python3Packages, nmigen, git }: + +python3Packages.buildPythonPackage { + name = "nmigen-soc"; + version = "harry-fork"; + + src = fetchgit { + # Using harry's fork + url = "https://github.com/HarryHo90sHK/nmigen-soc"; + rev = "89829244034d65d2bf486052af6646e9abec92fe"; + sha256 = "1dlvyr42shzh61rwj3xgkjr97lvra8vkh7qaxviml7wlw45sfc3h"; + + leaveDotGit = true; + }; + + nativeBuildInputs = [ python3Packages.setuptools_scm git ]; + propagatedBuildInputs = [ nmigen ]; + + meta = with stdenv.lib; { + description = "System on Chip toolkit for nMigen"; + homepage = "https://m-labs.hk"; + license = licenses.bsd2; + maintainers = [ maintainers.sb0 ]; + }; +} diff --git a/eda/nmigen-stdio.nix b/eda/nmigen-stdio.nix new file mode 100644 index 0000000..e5f5a63 --- /dev/null +++ b/eda/nmigen-stdio.nix @@ -0,0 +1,25 @@ +{ stdenv, fetchgit, python3Packages, nmigen, git }: + +python3Packages.buildPythonPackage { + name = "nmigen-stdio"; + version = "harry-fork"; + + src = fetchgit { + # Using harry's fork + url = "https://github.com/HarryHo90sHK/nmigen-stdio"; + rev = "531465f15e80b26a83989f843bda60e6c93babae"; + sha256 = "0ln32r7ldd0pndffw076xj6izl86mn5l9l9xmn5wcwrmiby294m1"; + + leaveDotGit = true; + }; + + nativeBuildInputs = [ python3Packages.setuptools_scm git ]; + propagatedBuildInputs = [ nmigen ]; + + meta = with stdenv.lib; { + description = "Industry standard I/O for nMigen"; + homepage = "https://m-labs.hk"; + license = licenses.bsd2; + maintainers = [ maintainers.sb0 ]; + }; +} diff --git a/examples/helloworld_ecp5.py b/examples/helloworld_ecp5.py deleted file mode 100644 index ba22ebd..0000000 --- a/examples/helloworld_ecp5.py +++ /dev/null @@ -1,58 +0,0 @@ -import argparse - -from nmigen import * -from nmigen_boards.versa_ecp5 import VersaECP5Platform - -from heavycomps import uart - - -class Top(Elaboratable): - def __init__(self, baudrate=115200): - self.baudrate = baudrate - - def elaborate(self, platform): - m = Module() - - cd_sync = ClockDomain(reset_less=True) - m.domains += cd_sync - m.d.comb += cd_sync.clk.eq(platform.request("clk100").i) - - string = "Hello World!\r\n" - mem = Memory(width=8, depth=len(string), - init=[ord(c) for c in string]) - m.submodules.rdport = rdport = mem.read_port(domain="comb") - - wait = Signal() - - tx = uart.RS232TX(round(2**32*self.baudrate/100e6)) - m.submodules.tx = tx - m.d.comb += [ - tx.stb.eq(~wait), - tx.data.eq(rdport.data), - platform.request("uart").tx.o.eq(tx.tx) - ] - - release = Signal() - counter = Signal(25) - m.d.sync += Cat(counter, release).eq(counter + 1) - with m.If(release): - m.d.sync += wait.eq(0) - - with m.If(~wait & tx.ack): - with m.If(rdport.addr == len(string) - 1): - m.d.sync += rdport.addr.eq(0) - m.d.sync += wait.eq(1) - with m.Else(): - m.d.sync += rdport.addr.eq(rdport.addr + 1) - - return m - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("build_dir") - args = parser.parse_args() - VersaECP5Platform().build(Top(), build_dir=args.build_dir) - -if __name__ == "__main__": - main() diff --git a/examples/helloworld_kintex7.nix b/examples/helloworld_kintex7.nix deleted file mode 100644 index e4c0d35..0000000 --- a/examples/helloworld_kintex7.nix +++ /dev/null @@ -1,9 +0,0 @@ -{ pkgs, hx }: - -pkgs.runCommand "helloworld-bitstream" { - buildInputs = [ (pkgs.python3.withPackages(ps: [hx.nmigen hx.nmigen-boards hx.heavycomps])) pkgs.yosys ]; - } - '' - export VIVADO=${hx.vivado}/bin/vivado - python ${./helloworld_kintex7.py} $out - '' diff --git a/examples/helloworld_kintex7.py b/examples/helloworld_kintex7.py deleted file mode 100644 index 582e44a..0000000 --- a/examples/helloworld_kintex7.py +++ /dev/null @@ -1,53 +0,0 @@ -import argparse - -from nmigen import * -from nmigen_boards.kc705 import KC705Platform - -from heavycomps import uart - - -class Top(Elaboratable): - def __init__(self, baudrate=115200): - self.baudrate = baudrate - self.clk156_p = Signal() - self.clk156_n = Signal() - self.serial_tx = Signal() - - def elaborate(self, platform): - m = Module() - - cd_sync = ClockDomain(reset_less=True) - m.domains += cd_sync - m.submodules.clock = Instance("BUFG", - i_I=platform.request("clk156").i, o_O=cd_sync.clk) - - string = "Hello World!\r\n" - mem = Memory(width=8, depth=len(string), - init=[ord(c) for c in string]) - m.submodules.rdport = rdport = mem.read_port(domain="comb") - - tx = uart.RS232TX(round(2**32*self.baudrate/156e6)) - m.submodules.tx = tx - m.d.comb += [ - tx.stb.eq(1), - tx.data.eq(rdport.data), - platform.request("uart").tx.o.eq(tx.tx) - ] - - with m.If(tx.ack): - with m.If(rdport.addr == len(string) - 1): - m.d.sync += rdport.addr.eq(0) - with m.Else(): - m.d.sync += rdport.addr.eq(rdport.addr + 1) - - return m - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("build_dir") - args = parser.parse_args() - KC705Platform().build(Top(), build_dir=args.build_dir) - -if __name__ == "__main__": - main() diff --git a/examples/simplesoc_ecp5.nix b/examples/simplesoc_ecp5.nix deleted file mode 100644 index dc8d3d9..0000000 --- a/examples/simplesoc_ecp5.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ pkgs, hx }: - -pkgs.runCommand "simplesoc-bitstream" { - buildInputs = [ (pkgs.python3.withPackages(ps: [hx.nmigen hx.nmigen-boards hx.heavycomps hx.minerva])) pkgs.yosys ]; - } - '' - export YOSYS=${pkgs.yosys}/bin/yosys - export NEXTPNR_ECP5=${pkgs.nextpnr}/bin/nextpnr-ecp5 - export ECPPACK=${pkgs.trellis}/bin/ecppack - python ${./simplesoc_ecp5.py} ${hx.fw-helloworld}/helloworld.bin $out - '' diff --git a/examples/simplesoc_ecp5.py b/examples/simplesoc_ecp5.py deleted file mode 100644 index 0eec864..0000000 --- a/examples/simplesoc_ecp5.py +++ /dev/null @@ -1,99 +0,0 @@ -import os -import argparse -import struct - -from nmigen import * -from nmigen.back import pysim -from nmigen_boards.versa_ecp5 import VersaECP5Platform - -from heavycomps import uart, wishbone -from minerva.core import Minerva - - -class SimpleWishboneSerial(Elaboratable): - def __init__(self, tx, sys_clk_freq, baudrate=115200): - self.tx = tx - self.bus = wishbone.Interface() - self.ftw = round(2**32*baudrate/sys_clk_freq) - - def elaborate(self, platform): - m = Module() - m.submodules.tx = tx = uart.RS232TX(self.ftw) - m.d.comb += [ - tx.stb.eq(self.bus.cyc & self.bus.stb & self.bus.we), - tx.data.eq(self.bus.dat_w), - self.bus.ack.eq(tx.ack), - self.tx.eq(tx.tx) - ] - return m - - -class Top(Elaboratable): - def __init__(self, firmware, simulate): - self.firmware = firmware - self.simulate = simulate - - def elaborate(self, platform): - m = Module() - - if self.simulate: - io_user_led = Signal() - io_uart_tx = Signal() - else: - cd_sync = ClockDomain(reset_less=True) - m.domains += cd_sync - m.d.comb += cd_sync.clk.eq(platform.request("clk100").i) - io_user_led = platform.request("led").o - io_uart_tx = platform.request("uart").tx.o - - counter = Signal(27) - m.d.sync += counter.eq(counter + 1) - m.d.comb += io_user_led.eq(counter[-1]) - - m.submodules.cpu = cpu = Minerva(with_icache=False, with_dcache=False, with_muldiv=False) - m.submodules.ram = ram = wishbone.SRAM(Memory(width=32, depth=1024, init=self.firmware)) - m.submodules.uart = uart = SimpleWishboneSerial(io_uart_tx, 100e6) - m.submodules.con = con = wishbone.InterconnectShared( - [cpu.ibus, cpu.dbus], - [ - (lambda a: ~a[20], ram.bus), - (lambda a: a[20], uart.bus) - ], register=True) - - return m - - -def read_firmware(file): - firmware = [] - with open(file, "rb") as f: - while True: - word = f.read(4) - if len(word) < 4: - break - firmware.append(struct.unpack(" ! { - loop { - print("hello2"); - } -} diff --git a/firmware/Cargo.lock b/firmware/testing/Cargo.lock similarity index 100% rename from firmware/Cargo.lock rename to firmware/testing/Cargo.lock diff --git a/firmware/Cargo.toml b/firmware/testing/Cargo.toml similarity index 100% rename from firmware/Cargo.toml rename to firmware/testing/Cargo.toml diff --git a/firmware/build.rs b/firmware/testing/build.rs similarity index 100% rename from firmware/build.rs rename to firmware/testing/build.rs diff --git a/firmware/default.nix b/firmware/testing/default.nix similarity index 91% rename from firmware/default.nix rename to firmware/testing/default.nix index 043ac48..a2cab54 100644 --- a/firmware/default.nix +++ b/firmware/testing/default.nix @@ -19,6 +19,6 @@ rustPlatform.buildRustPackage rec { installPhase = '' mkdir -p $out cp target/riscv32i-unknown-none-elf/release/helloworld $out - ${binutils-riscv32}/bin/riscv32-unknown-elf-objcopy -O binary target/riscv32i-unknown-none-elf/release/helloworld $out/helloworld.bin + ${binutils-riscv32}/bin/riscv32-unknown-elf-objcopy -O binary target/riscv32i-unknown-none-elf/release/helloworld $out/testing.bin ''; } diff --git a/firmware/testing/memory.x b/firmware/testing/memory.x new file mode 100644 index 0000000..7e6daff --- /dev/null +++ b/firmware/testing/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 64K + RAM : ORIGIN = 0x00004000, LENGTH = 16K +} diff --git a/firmware/testing/src/eth.rs b/firmware/testing/src/eth.rs new file mode 100644 index 0000000..e052ec0 --- /dev/null +++ b/firmware/testing/src/eth.rs @@ -0,0 +1,45 @@ +use core::ptr::{read_volatile, write_volatile}; + +const ETH_FLAGS : *mut u32 = (0x02000200) as *mut u32; +const ETH_RXDATA: *mut u32 = (0x02000204) as *mut u32; + +pub fn eth_get_rxready() -> u8 { + unsafe { + let full = read_volatile(ETH_FLAGS) as u32; + return (full & 0x0001) as u8; + } +} +pub fn eth_enable_rx_clear() { + unsafe { + let bitmasked = read_volatile(ETH_FLAGS) & (!0x0100) as u32; + write_volatile(ETH_FLAGS, (0x0100 | bitmasked) as u32); + } +} +pub fn eth_disable_rx_clear() { + unsafe { + let bitmasked = read_volatile(ETH_FLAGS) & (!0x0100) as u32; + write_volatile(ETH_FLAGS, (0x0000 | bitmasked) as u32); + } +} +pub fn eth_read_rxdat() -> u32 { + unsafe { + return read_volatile(ETH_RXDATA) as u32; + } +} + +// Helper functions +pub fn eth_get_byte_from_dat(dat: u32) -> u8 { + unsafe { + return (dat & 0x00FF) as u8; + } +} +pub fn eth_get_eop_from_dat(dat: u32) -> u8 { + unsafe { + return ((dat & 0x0100) >> 8) as u8; + } +} +pub fn eth_get_nodat_from_dat(dat: u32) -> u8 { + unsafe { + return ((dat & 0x0200) >> 9) as u8; + } +} \ No newline at end of file diff --git a/firmware/testing/src/gpio.rs b/firmware/testing/src/gpio.rs new file mode 100644 index 0000000..437bc36 --- /dev/null +++ b/firmware/testing/src/gpio.rs @@ -0,0 +1,27 @@ +use core::ptr::{read_volatile, write_volatile}; + +const GPIO_SWITCH: *mut u32 = (0x02000010) as *mut u32; + +pub fn gpio_led_on(index: u32) { + unsafe { + let bitmask = 1 << index as u32; + let bitmasked = read_volatile(GPIO_SWITCH) & (0xFFFF - bitmask) as u32; + write_volatile(GPIO_SWITCH, ((1 << index) | bitmasked) as u32); + } +} +pub fn gpio_led_off(index: u32) { + unsafe { + let bitmask = 1 << index as u32; + let bitmasked = read_volatile(GPIO_SWITCH) & (0xFFFF - bitmask) as u32; + write_volatile(GPIO_SWITCH, ((0 << index) | bitmasked) as u32); + } +} +pub fn gpio_led_toggle(index: u32) { + unsafe { + let raw = read_volatile(GPIO_SWITCH) as u32; + let bitmask = 1 << index as u32; + let bitmasked = raw & (!bitmask) as u32; + let current = raw & bitmask as u32; + write_volatile(GPIO_SWITCH, (!current & bitmask | bitmasked) as u32); + } +} diff --git a/firmware/testing/src/main.rs b/firmware/testing/src/main.rs new file mode 100644 index 0000000..7aeed71 --- /dev/null +++ b/firmware/testing/src/main.rs @@ -0,0 +1,310 @@ +#![no_std] +#![no_main] + +extern crate riscv_rt; +extern crate panic_halt; + +use riscv_rt::entry; + +mod uart; +mod gpio; +mod timer; +mod spiflash; +mod eth; + +#[entry] +fn main() -> ! { + uart::uart_enable_rx(); + println!(""); + println!(" * * HeavyX Testing Firmware * *"); + println!(""); + + loop { + println!("--------------------------------------------------"); + println!(""); + println!(" [Main Menu]"); + println!(""); + println!(" Type a number to select function:"); + println!(" [1] UART Typewriter"); + println!(" [2] SPI Flash Reader"); + println!(" [3] Ethernet Packet Viewer"); + println!(" [4] LED Blinker"); + print!(">> "); + loop { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + if c == b'1' { print!("{}\n", c as char); uart_test(); break; } + if c == b'2' { print!("{}\n", c as char); spi_test(); break; } + if c == b'3' { print!("{}\n", c as char); eth_test(); break; } + if c == b'4' { print!("{}\n", c as char); blink_test(); break; } + } + } + } +} + +fn uart_test() { + println!("--------------------------------------------------"); + println!(""); + println!(" [UART Typewriter]"); + println!(""); + println!(" Type to start writing."); + println!(" Backspacing is disabled."); + println!(" Press [Ctrl]+[D] to exit."); + println!(".................................................."); + loop { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + if c == b'\r' { print!("\n") } + else if c == 0x04 { print!("\n"); break; } + else if c > 0x1f && c != 0x7f { print!("{}", c as char) } + } + } +} + +fn spi_test() { + println!("--------------------------------------------------"); + println!(""); + println!(" [SPI Flash Reader]"); + println!(""); + println!(" Press - / + to change start address."); + println!(" Press [ / ] to adjust read size."); + println!(" Press , / . to adjust SPI divisor."); + println!(" Press [Enter] to read."); + println!(" Press [Ctrl]+[D] to exit."); + let mut spi_read_addr = 0x000000 as u32; + let mut spi_read_size = 512 as u32; + loop { + let mut spi_div = spiflash::flash_get_div() as u32; + println!(">> [ 0x{:06x} - 0x{:06x} ({} bytes) @ divisor = {} ]", + spi_read_addr, spi_read_addr + spi_read_size - 1, spi_read_size, spi_div); + let mut spi_exit = 0 as u8; + loop { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + // Change address + if c == b'-' || c == b'+' { + if c == b'-' && spi_read_addr > 0 { + if spi_read_addr < spi_read_size { spi_read_addr = 0 } + else { spi_read_addr -= spi_read_size } + } + if c == b'+' && spi_read_addr < 0xffffff { + if spi_read_addr >= 0x1000000 - spi_read_size { spi_read_addr = 0x1000000 - spi_read_size} + else { spi_read_addr += spi_read_size } + } + break; + } + // Change read size + if c == b'[' || c == b']' { + if c == b'[' && spi_read_size > 16 { + spi_read_size >>= 1 + } + if c == b']' && spi_read_addr + (spi_read_size << 1) <= 0x1000000 { + spi_read_size <<= 1 + } + break; + } + // Change divisor + if c == b',' || c == b'.' { + if c == b',' && spi_div > 1 { spiflash::flash_set_div(spi_div-1) } + if c == b'.' && spi_div < 0xffff { spiflash::flash_set_div(spi_div+1) } + break; + } + // Read + if c == b'\r' { + for x in (spi_read_addr..(spi_read_addr+spi_read_size)).step_by(16) { + println!(" 0x{:06x} : {:08x} {:08x} {:08x} {:08x}", x, + spiflash::flash_read_word(x), spiflash::flash_read_word(x+4), + spiflash::flash_read_word(x+8), spiflash::flash_read_word(x+12)); + } + break; + } + else if c == 0x04 { spi_exit = 1; break; } + } + } + if spi_exit == 1 { break; } + } +} + +fn eth_test() { + println!("--------------------------------------------------"); + println!(""); + println!(" [Ethernet Packet Viewer]"); + println!(""); + println!(" Automatically receiving incoming packets ..."); + println!(" Press [Ctrl]+[D] to exit."); + println!(""); + let mut eth_rx_count = 0 as u32; + let mut eth_rx_start = 1 as u32; + loop { + let mut j = 0; + let mut byte_count = 0; + let mut eth_exit = 0 as u8; + loop { + if eth::eth_get_rxready() == 1 { + // Read a byte from the packet + let rxdat = eth::eth_read_rxdat(); + // If reading returns no data, accept UART interrupt + if eth::eth_get_nodat_from_dat(rxdat) == 1 && eth_rx_start == 1 { + timer::timer_set(5000000, 0); + timer::timer_enable(); + while timer::timer_get() != 0 { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + if c == 0x04 { eth_exit = 1; timer::timer_disable(); break; } + } + } + timer::timer_disable(); + } + // Otherwise, return byte + else { + eth_rx_start = 0; + if byte_count == 0 { + println!(">> New eth packet # {}:", eth_rx_count); + } + if j == 0 { print!(" ") } + if j == 8 { print!(" ") } + print!("{:02x} ", eth::eth_get_byte_from_dat(rxdat)); + byte_count += 1; + if eth::eth_get_eop_from_dat(rxdat) == 1 { + print!("\n"); + j = 0; + eth_rx_start = 1; + eth_rx_count += 1; + break; + } + if j == 15 { print!("\n"); j = 0; } + else { j += 1; } + } + } + else { + println!("\n>> Buffer is full, clearing current buffer..."); + eth::eth_enable_rx_clear(); + while eth::eth_get_rxready() == 0 { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + if c == 0x04 { eth_exit = 1; timer::timer_disable(); break; } + } + } + eth::eth_disable_rx_clear(); + println!(">> Buffer cleared. Abandoned eth packet # {}:", eth_rx_count); + j = 0; + byte_count = 0; + eth_rx_count += 1; + } + if eth_exit == 1 { break; } + } + if eth_exit == 1 { break; } + } +} + +fn blink_test() { + println!("--------------------------------------------------"); + println!(""); + println!(" [LED Blinker]"); + println!(""); + println!(" Each LED will blink once first (primary),"); + println!(" and then twice (secondary)."); + println!(" Press - / + to adjust primary blink time."); + println!(" Press , / . to adjust secondary blink time."); + println!(" Press [Ctrl]+[D] to stop and exit."); + println!(""); + for i in 0..8 { + gpio::gpio_led_off(i); + } + let mut timer_load_primary = 50000000 as u32; + let mut timer_load_secondary = 12500000 as u32; + println!(">> [ Primary @ {} clocks ]", timer_load_primary); + println!(" [ Secondary @ {} clocks ]", timer_load_secondary); + let mut blink_exit = 0 as u8; + loop { + for i in 0..8 { + println!(">> Blinking LED #{}", i+1); + timer::timer_set(timer_load_primary, 0); + timer::timer_enable(); + gpio::gpio_led_toggle(i); + while timer::timer_get() != 0 { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + if c == b'-' || c == b'+' { + if c == b'-' && timer_load_primary > 5000000 { timer_load_primary >>= 1 } + if c == b'+' && timer_load_primary < 0xffffffff { timer_load_primary <<= 1 } + } + if c == b',' || c == b'.' { + if c == b',' && timer_load_secondary > 5000000 { timer_load_secondary >>= 1 } + if c == b'.' && timer_load_secondary < 0xffffffff { timer_load_secondary <<= 1 } + } + else if c == 0x04 { blink_exit = 1; timer::timer_disable(); break; } + println!(">> [ Primary @ {} clocks ]", timer_load_primary); + println!(" [ Secondary @ {} clocks ]", timer_load_secondary); + } + } + timer::timer_disable(); // Reset to LOAD + timer::timer_enable(); + gpio::gpio_led_toggle(i); + while timer::timer_get() != 0 { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + if c == b'-' || c == b'+' { + if c == b'-' && timer_load_primary > 5000000 { timer_load_primary >>= 1 } + if c == b'+' && timer_load_primary < 0xffffffff { timer_load_primary <<= 1 } + } + if c == b',' || c == b'.' { + if c == b',' && timer_load_secondary > 5000000 { timer_load_secondary >>= 1 } + if c == b'.' && timer_load_secondary < 0xffffffff { timer_load_secondary <<= 1 } + } + else if c == 0x04 { blink_exit = 1; timer::timer_disable(); break; } + println!(">> [ Primary @ {} clocks ]", timer_load_primary); + println!(" [ Secondary @ {} clocks ]", timer_load_secondary); + } + } + timer::timer_disable(); // Reset to LOAD + for __ in 0..2 { + timer::timer_set(timer_load_secondary, 0); + timer::timer_enable(); + gpio::gpio_led_toggle(i); + while timer::timer_get() != 0 { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + if c == b'-' || c == b'+' { + if c == b'-' && timer_load_primary > 5000000 { timer_load_primary >>= 1 } + if c == b'+' && timer_load_primary < 0xffffffff { timer_load_primary <<= 1 } + } + if c == b',' || c == b'.' { + if c == b',' && timer_load_secondary > 5000000 { timer_load_secondary >>= 1 } + if c == b'.' && timer_load_secondary < 0xffffffff { timer_load_secondary <<= 1 } + } + else if c == 0x04 { blink_exit = 1; timer::timer_disable(); break; } + println!(">> [ Primary @ {} clocks ]", timer_load_primary); + println!(" [ Secondary @ {} clocks ]", timer_load_secondary); + } + } + timer::timer_disable(); // Reset to LOAD + timer::timer_enable(); + gpio::gpio_led_toggle(i); + while timer::timer_get() != 0 { + if uart::uart_get_rxready() == 1 { + let c = uart::uart_read_byte() as u8; + if c == b'-' || c == b'+' { + if c == b'-' && timer_load_primary > 5000000 { timer_load_primary >>= 1 } + if c == b'+' && timer_load_primary < 0xffffffff { timer_load_primary <<= 1 } + } + if c == b',' || c == b'.' { + if c == b',' && timer_load_secondary > 5000000 { timer_load_secondary >>= 1 } + if c == b'.' && timer_load_secondary < 0xffffffff { timer_load_secondary <<= 1 } + } + else if c == 0x04 { blink_exit = 1; timer::timer_disable(); break; } + println!(">> [ Primary @ {} clocks ]", timer_load_primary); + println!(" [ Secondary @ {} clocks ]", timer_load_secondary); + } + } + timer::timer_disable(); // Reset to LOAD + } + gpio::gpio_led_off(i); // Turn off the current LED + if blink_exit == 1 { break; } + } + if blink_exit == 1 { break; } + } + for i in 0..8 { + gpio::gpio_led_off(i); + } +} diff --git a/firmware/testing/src/spiflash.rs b/firmware/testing/src/spiflash.rs new file mode 100644 index 0000000..f365f42 --- /dev/null +++ b/firmware/testing/src/spiflash.rs @@ -0,0 +1,22 @@ +use core::ptr::{read_volatile, write_volatile}; + +const FLASH_CONTROL: *mut u32 = (0x02000100) as *mut u32; +const FLASH_START_ADDR: u32 = (0x03000000); + +pub fn flash_set_div(div: u32) { + unsafe { + write_volatile(FLASH_CONTROL, div as u32); + } +} +pub fn flash_get_div() -> u32 { + unsafe { + return read_volatile(FLASH_CONTROL) as u32 + } +} + +pub fn flash_read_word(byte_offset: u32) -> u32 { + let mem = (FLASH_START_ADDR + byte_offset) as *mut u32; + unsafe { + return *mem + } +} \ No newline at end of file diff --git a/firmware/testing/src/timer.rs b/firmware/testing/src/timer.rs new file mode 100644 index 0000000..79bc714 --- /dev/null +++ b/firmware/testing/src/timer.rs @@ -0,0 +1,30 @@ +use core::ptr::{read_volatile, write_volatile}; + +const TIMER_CONTROL: *mut u32 = (0x02000020) as *mut u32; +const TIMER_LOAD : *mut u32 = (0x02000024) as *mut u32; +const TIMER_RELOAD : *mut u32 = (0x02000028) as *mut u32; +const TIMER_CURRENT: *mut u32 = (0x0200002C) as *mut u32; + +pub fn timer_enable() { + unsafe { + let bitmasked = read_volatile(TIMER_CONTROL) & (!0x11) as u32; + write_volatile(TIMER_CONTROL, (0x11 | bitmasked) as u32); + } +} +pub fn timer_disable() { + unsafe { + let bitmasked = read_volatile(TIMER_CONTROL) & (!0x11) as u32; + write_volatile(TIMER_CONTROL, (0x00 | bitmasked) as u32); + } +} +pub fn timer_set(load: u32, reload: u32) { + unsafe { + write_volatile(TIMER_LOAD, load as u32); + write_volatile(TIMER_RELOAD, reload as u32); + } +} +pub fn timer_get() -> u32 { + unsafe { + return read_volatile(TIMER_CURRENT) as u32 + } +} diff --git a/firmware/testing/src/uart.rs b/firmware/testing/src/uart.rs new file mode 100644 index 0000000..1581c6a --- /dev/null +++ b/firmware/testing/src/uart.rs @@ -0,0 +1,78 @@ +use core::ptr::{read_volatile, write_volatile}; +use core::fmt; + +const UART_CONTROL: *mut u32 = (0x02000000) as *mut u32; +const UART_FLAGS : *mut u32 = (0x02000004) as *mut u32; +const UART_TXDATA : *mut u32 = (0x02000008) as *mut u32; +const UART_RXDATA : *mut u32 = (0x0200000C) as *mut u32; + +pub fn uart_set_div(div: u32) { + unsafe { + write_volatile(UART_CONTROL, div as u32); + } +} +pub fn uart_get_div() -> u32 { + unsafe { + return read_volatile(UART_CONTROL) as u32 + } +} +pub fn uart_enable_rx() { + unsafe { + let bitmasked = read_volatile(UART_FLAGS) & (!0x0100) as u32; + write_volatile(UART_FLAGS, (0x0100 | bitmasked) as u32); + } +} +pub fn uart_disable_rx() { + unsafe { + let bitmasked = read_volatile(UART_FLAGS) & (!0x0100) as u32; + write_volatile(UART_FLAGS, (0x0000 | bitmasked) as u32); + } +} +pub fn uart_get_rxready() -> u8 { + unsafe { + let full = read_volatile(UART_FLAGS) as u32; + return (full & 0x0001) as u8; + } +} +pub fn uart_read_byte() -> u8 { + unsafe { + return read_volatile(UART_RXDATA) as u8; + } +} +pub fn uart_get_txready() -> u8 { + unsafe { + let full = read_volatile(UART_FLAGS) as u32; + return ((full & 0x0010) >> 4) as u8; + } +} +pub fn uart_write_byte(c: u8) { + unsafe { + write_volatile(UART_TXDATA, c as u32); + } +} + +pub struct Console; + +impl fmt::Write for Console { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + for c in s.bytes() { + while uart_get_txready() == 0 {} + uart_write_byte(c); + } + Ok(()) + } +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ({ + use core::fmt::Write; + write!($crate::uart::Console, $($arg)*).unwrap() + }) +} + +#[macro_export] +macro_rules! println { + ($fmt:expr) => (print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => (print!(concat!($fmt, "\n"), $($arg)*)); +} diff --git a/heavycomps.nix b/heavycomps.nix index 4442112..08e002f 100644 --- a/heavycomps.nix +++ b/heavycomps.nix @@ -1,11 +1,11 @@ -{ stdenv, python3Packages, nmigen }: +{ stdenv, python3Packages, nmigen, nmigen-stdio, nmigen-soc }: python3Packages.buildPythonPackage { name = "heavycomps"; src = ./heavycomps; - propagatedBuildInputs = [ nmigen ]; + propagatedBuildInputs = [ nmigen nmigen-stdio nmigen-soc ]; meta = with stdenv.lib; { description = "Components for the HeavyX SoC toolkit"; diff --git a/heavycomps/heavycomps/cfgs/__init__.py b/heavycomps/heavycomps/cfgs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/heavycomps/heavycomps/cfgs/ecp5.py b/heavycomps/heavycomps/cfgs/ecp5.py new file mode 100644 index 0000000..36b3069 --- /dev/null +++ b/heavycomps/heavycomps/cfgs/ecp5.py @@ -0,0 +1,106 @@ +from nmigen import * + + +__all__ = ["EthRGMIICoreCfg", "SPIFlashCoreCfg"] + + +class EthRGMIICoreCfg(Elaboratable): + def __init__(self, core, eth_pins, *, rx, tx, tx_clk=None): + self.core = core + self.eth_pins = eth_pins + if not isinstance(rx, bool): + raise TypeError("Must include either rx=True or False " + "to indicate whether RX is used or not, not rx={}" + .format(repr(rx))) + self.rx = rx + if not isinstance(tx, bool): + raise TypeError("Must include either tx=True or False " + "to indicate whether TX is used or not, not tx={}" + .format(repr(tx))) + self.tx = tx + + if tx: + if tx_clk is None: + raise TypeError("Signal as TX clock must not be None") + self.tx_clk = tx_clk + + def elaborate(self, platform): + m = Module() + + if self.rx: + rx = self.core.rx_io + # DDRInput on rx_ctl + m.submodules += [ + Instance("IDDRX1F", + i_D=self.eth_pins.rx_ctl, + i_SCLK=rx.rx_ctl_iddr.i_clk, + o_Q0=rx.rx_ctl_iddr.i0, + o_Q1=rx.rx_ctl_iddr.i1, + ) + ] + # DDRInput on rx_data + for i in range(4): + m.submodules += [ + Instance("IDDRX1F", + i_D=self.eth_pins.rx_data[i], + i_SCLK=rx.rx_data_iddr.i_clk, + o_Q0=rx.rx_data_iddr.i0[i], + o_Q1=rx.rx_data_iddr.i1[i], + ) + ] + # RX Clock + cd_eth_rx = ClockDomain("eth_rx") + m.domains += cd_eth_rx + m.d.comb += cd_eth_rx.clk.eq(self.eth_pins.rx_clk) + + if self.tx: + tx = self.core.tx_io + # DDROutput on tx_clk + m.submodules += [ + Instance("ODDRX1F", + i_D0=tx.tx_clk_oddr.o0, + i_D1=tx.tx_clk_oddr.o1, + i_SCLK=tx.tx_clk_oddr.o_clk, + o_Q=self.eth_pins.tx_clk + ) + ] + # DDROutput on tx_ctl + m.submodules += [ + Instance("ODDRX1F", + i_D0=tx.tx_ctl_oddr.o0, + i_D1=tx.tx_ctl_oddr.o1, + i_SCLK=tx.tx_ctl_oddr.o_clk, + o_Q=self.eth_pins.tx_ctl + ) + ] + # DDROutput on tx_data + for i in range(4): + m.submodules += [ + Instance("ODDRX1F", + i_D0=tx.tx_data_oddr.o0[i], + i_D1=tx.tx_data_oddr.o1[i], + i_SCLK=tx.tx_data_oddr.o_clk, + o_Q=self.eth_pins.tx_data[i] + ) + ] + # TX Clock + cd_eth_tx = ClockDomain("eth_tx") + m.domains += cd_eth_tx + m.d.comb += cd_eth_tx.clk.eq(self.tx_clk) + + return m + + +class SPIFlashCoreCfg(Elaboratable): + def __init__(self, core): + self.core = core + + def elaborate(self, platform): + m = Module() + + spi = self.core.spi_io + m.submodules += Instance("USRMCLK", + i_USRMCLKI=spi.clk, + i_USRMCLKTS=0) + + return m diff --git a/heavycomps/heavycomps/cores/__init__.py b/heavycomps/heavycomps/cores/__init__.py new file mode 100644 index 0000000..3230958 --- /dev/null +++ b/heavycomps/heavycomps/cores/__init__.py @@ -0,0 +1,5 @@ +from .uart import * +from .gpio import * +from .timer import * +from .spiflash import * +from .eth_rgmii import * diff --git a/heavycomps/heavycomps/cores/eth_rgmii.py b/heavycomps/heavycomps/cores/eth_rgmii.py new file mode 100644 index 0000000..4fd9cf3 --- /dev/null +++ b/heavycomps/heavycomps/cores/eth_rgmii.py @@ -0,0 +1,159 @@ +from nmigen import * +from nmigen_stdio.eth import rgmii +from nmigen_soc import csr, wishbone +from nmigen.utils import * + +from nmigen.lib.fifo import AsyncFIFO +from nmigen.lib.cdc import FFSynchronizer, ResetSynchronizer + + +__all__ = ["EthRGMIICore"] + + +class EthRGMIICore(Elaboratable): + def __init__(self, *, rx, tx, bus_data_width, + rx_buffer_size=2**15, tx_buffer_size=2**15, + bus_type="wishbone", bus_granularity=None, **kwargs): + if not isinstance(rx, bool): + raise TypeError("Must include either rx=True or False " + "to indicate whether RX is used or not, not rx={}" + .format(repr(rx))) + self.rx = rx + if not isinstance(tx, bool): + raise TypeError("Must include either tx=True or False " + "to indicate whether TX is used or not, not tx={}" + .format(repr(tx))) + self.tx = tx + if bus_data_width < 9: + raise ValueError("Bus data width must be at least 10, not {}" + .format(bus_data_width)) + + if bus_type not in ["wishbone"]: + raise ValueError("Bus type must be " + "\"wishbone\", not {}".format(bus_type)) + self.bus_type = bus_type + if bus_type == "wishbone": + self.rxdat_bus = wishbone.Interface( + addr_width=0, + data_width=bus_data_width, + granularity=bus_granularity, + **kwargs + ) + + if rx: + self.rx_buffer_size = rx_buffer_size + self.rx_stb = Signal() + self.rx_eop = Signal() + self.rx_io = rgmii.EthRGMIIRX(clk_domain="eth_rx") + + if tx: + self.tx_buffer_size = tx_buffer_size + self.tx_stb = Signal() + self.tx_eop = Signal() + self.tx_io = rgmii.EthRGMIITX(clk_domain="eth_tx") + + bus_dw = bus_data_width + if bus_granularity is None: + bus_granularity = data_width + bus_gr = bus_granularity + + with csr.Bank(name="eth", addr_width=max(1, log2_int(bus_dw//bus_gr)), + data_width=bus_gr, type="mux") as self.csr: + self.csr.r += csr.Register("flags", "rw", width=bus_dw) + with self.csr.r.flags as reg: + if rx: + reg.f += [ + csr.Field("rx_ready", "r", startbit=0, reset_value=1), + csr.Field("rx_clear", "rw", startbit=8), + ] + if tx: + reg += [ + csr.Field("tx_ready", "r", startbit=4, reset_value=1), + csr.Field("tx_send", "rw", startbit=12) + ] + self.wb2csr = csr.WishboneCSRBridge(self.csr.mux.bus, data_width=bus_data_width) + self.csr_bus = self.wb2csr.wb_bus + + def elaborate(self, platform): + m = Module() + + if self.rx: + m.submodules.rx = rx = self.rx_io + if self.tx: + m.submodules.tx = tx = self.tx_io + + m.submodules += self.csr, self.wb2csr + + ########### + # RX + if self.rx: + # RX FIFO ([7:0]=data ; [8]=is eop?) + rx_fifo = AsyncFIFO(width=9, depth=self.rx_buffer_size) + m.submodules += DomainRenamer({"write": "eth_rx", "read": "sync"})(rx_fifo) + rx_clearing = Signal() + m.d.comb += [ + # Write to FIFO + # RX stops when clearing buffer + rx_fifo.w_en.eq(rx.source.stb & ~rx_clearing), + rx_fifo.w_data.eq(Cat(rx.source.payload.data, rx.source.eop)), + ] + + if self.bus_type == "wishbone": + # Reading while clearing buffer would return invalid data + with m.If(~rx_clearing): + rxdat_r_en = Signal() + m.d.comb += [ + rxdat_r_en.eq(self.rxdat_bus.cyc & self.rxdat_bus.stb & ~self.rxdat_bus.we & + ~self.rxdat_bus.ack & rx_fifo.r_rdy) + ] + # Read from FIFO + m.d.sync += rx_fifo.r_en.eq(rxdat_r_en) + with m.If(self.rxdat_bus.sel[0] & rxdat_r_en): + m.d.sync += self.rxdat_bus.dat_r.eq(rx_fifo.r_data) + with m.Else(): + m.d.sync += self.rxdat_bus.dat_r.eq(Cat(Repl(0, 9), 1)) # [9]=no data? + # RX data bus ACK + with m.If(self.rxdat_bus.cyc & self.rxdat_bus.stb & ~self.rxdat_bus.we): + m.d.sync += self.rxdat_bus.ack.eq(1) + with m.If(self.rxdat_bus.ack): + m.d.sync += self.rxdat_bus.ack.eq(0) + + # CSR - Buffer is ready + # Set to 0 once buffer is full + # Resets to 1 only when buffer has been completely cleared + rx_ready_prev = Signal(reset=1) + m.d.comb += self.csr.r.flags.f.rx_ready.set_stb.eq(1) + with m.If(~rx_fifo.w_rdy): + m.d.comb += self.csr.r.flags.f.rx_ready.set_val.eq(0) + m.d.sync += rx_ready_prev.eq(0) + with m.Else(): + m.d.comb += self.csr.r.flags.f.rx_ready.set_val.eq(rx_ready_prev) + with m.If(~rx_ready_prev & ~rx_fifo.r_rdy): + m.d.sync += rx_ready_prev.eq(1) + + # CSR - Buffer clearing + # Starts clearing after flag is set to 1 + # Ignores flag during clearing + with m.If(self.csr.r.flags.f.rx_clear.s & ~rx_clearing & rx_fifo.r_rdy): + m.d.sync += [ + rx_fifo.r_en.eq(1), + rx_clearing.eq(1) + ] + with m.If(rx_clearing & ~rx_fifo.r_rdy): + m.d.sync += [ + rx_fifo.r_en.eq(0), + rx_clearing.eq(0) + ] + + # Useful signals + m.submodules += [ + FFSynchronizer(rx.source.stb, self.rx_stb), + FFSynchronizer(rx.source.eop, self.rx_eop), + ] + + ########### + # TX + if self.tx: + raise NotImplementedError("TX logic has not been implemented") + + return m diff --git a/heavycomps/heavycomps/cores/gpio.py b/heavycomps/heavycomps/cores/gpio.py new file mode 100644 index 0000000..a49b143 --- /dev/null +++ b/heavycomps/heavycomps/cores/gpio.py @@ -0,0 +1,43 @@ +from nmigen import * +from nmigen_soc import csr +from nmigen.utils import * + + +__all__ = ["GPIOOutput"] + + +class GPIOOutput(Elaboratable): + def __init__(self, gpio_out, *, bus_data_width, count=8, bus_granularity=None): + self.gpio_out = gpio_out + self.count = count + + bus_dw = bus_data_width + if bus_granularity is None: + bus_granularity = data_width + bus_gr = bus_granularity + + with csr.Bank(name="gpio", + addr_width=max(1, log2_int(-(-count//bus_gr), need_pow2=False)), + data_width=bus_gr, type="mux") as self.csr: + self.csr.r += [ + csr.Register( + "switch", "rw", width=count, + fields=[ + csr.Field("output_{}".format(i)) for i in range(count) + ] + ) + ] + self.wb2csr = csr.WishboneCSRBridge(self.csr.mux.bus, data_width=bus_data_width) + self.csr_bus = self.wb2csr.wb_bus + + def elaborate(self, platform): + m = Module() + + m.submodules += self.csr, self.wb2csr + + m.d.comb += [ + self.gpio_out[i].eq(self.csr.r.switch.s[i]) + for i in range(self.count) + ] + + return m diff --git a/heavycomps/heavycomps/cores/spiflash.py b/heavycomps/heavycomps/cores/spiflash.py new file mode 100644 index 0000000..3fe864e --- /dev/null +++ b/heavycomps/heavycomps/cores/spiflash.py @@ -0,0 +1,94 @@ +from nmigen import * +from nmigen_stdio import spiflash +from nmigen_soc import csr, wishbone +from nmigen.utils import * + + +__all__ = ["SPIFlashCore"] + + +class SPIFlashCore(Elaboratable): + def __init__(self, spi_pins, *, spi_protocol, read_type, + addr_width, sys_clk_freq, freq, + data_width=32, divisor_bits=32, + bus_type="wishbone", granularity=None, **kwargs): + self.spi_pins = spi_pins + self.div = int(sys_clk_freq/freq) - 1 + self.div_bits = divisor_bits + + if read_type not in ["slow", "fast"]: + raise ValueError("Type of the read operation must be one of " + "\"slow\" or \"fast\", not {}".format(read_type)) + if read_type == "slow": + self.spi_io = spiflash.SPIFlashSlowReader( + protocol=spi_protocol, + addr_width=addr_width, + data_width=data_width, + divisor_bits=divisor_bits, + divisor=self.div, + pins=self.spi_pins + ) + elif read_type == "fast": + self.spi_io = spiflash.SPIFlashFastReader( + protocol=spi_protocol, + addr_width=addr_width, + data_width=data_width, + divisor_bits=divisor_bits, + divisor=self.div, + pins=self.spi_pins + ) + + if bus_type not in ["wishbone"]: + raise ValueError("Bus type must be " + "\"wishbone\", not {}".format(bus_type)) + self.bus_type = bus_type + if bus_type == "wishbone": + if granularity is None: + granularity = data_width + self.bus = wishbone.Interface( + addr_width=addr_width - log2_int(data_width//granularity), + data_width=data_width, + granularity=granularity, + **kwargs + ) + + bus_dw = data_width + if granularity is None: + granularity = data_width + self.granularity = bus_gr = granularity + + with csr.Bank(name="spi", addr_width=max(1, log2_int(bus_dw//bus_gr)), + data_width=bus_gr, type="mux") as self.csr: + self.csr.r += [ + csr.Register( + "control", "rw", width=bus_dw, + fields=[csr.Field("divisor", "rw", width=self.div_bits, reset_value=self.div)] + ) + ] + self.wb2csr = csr.WishboneCSRBridge(self.csr.mux.bus, data_width=data_width) + self.csr_bus = self.wb2csr.wb_bus + + def elaborate(self, platform): + m = Module() + + m.submodules.spi = spi_io = self.spi_io + m.submodules += self.csr, self.wb2csr + + # CSR - Divisor + m.d.comb += spi_io.divisor.eq(self.csr.r.control.f.divisor.s) + + # non-CSR - Return Memory + if self.bus_type == "wishbone": + m.d.comb += [ + spi_io.ack.eq(self.bus.cyc & self.bus.stb & ~self.bus.we + & spi_io.rdy), + spi_io.addr.eq(self.bus.adr), + self.bus.ack.eq(spi_io.r_rdy) + ] + for i, sel_index in enumerate(self.bus.sel): + m.d.comb += [ + self.bus.dat_r[i*self.granularity:(i+1)*self.granularity] + .eq(spi_io.r_data[i*self.granularity:(i+1)*self.granularity]), + ] + + return m diff --git a/heavycomps/heavycomps/cores/timer.py b/heavycomps/heavycomps/cores/timer.py new file mode 100644 index 0000000..0050a6c --- /dev/null +++ b/heavycomps/heavycomps/cores/timer.py @@ -0,0 +1,68 @@ +from nmigen import * +from nmigen_soc import csr +from nmigen.utils import * + + +__all__ = ["TimerCore"] + + +class TimerCore(Elaboratable): + def __init__(self, *, width, bus_data_width, + clk_domain="sync", bus_granularity=None): + self.width = width + + bus_dw = bus_data_width + if bus_granularity is None: + bus_granularity = bus_data_width + bus_gr = bus_granularity + + with csr.Bank(name="timer", + addr_width=log2_int(-(-(bus_dw+3*max(width, bus_dw))//bus_gr), need_pow2=False), + data_width=bus_gr) as self.csr: + self.csr.r += [ + csr.Register( + "control", "rw", width=bus_dw, + fields=[csr.Field("enable", startbit=0), + csr.Field("update_enable", startbit=4)] + ), + csr.Register( + "load", "rw", width=max(width, bus_dw), + fields=[csr.Field("value", width=self.width)] + ), + csr.Register( + "reload", "rw", width=max(width, bus_dw), + fields=[csr.Field("value", width=self.width)] + ), + csr.Register( + "current", "r", width=max(width, bus_dw), + fields=[csr.Field("value", width=self.width)] + ) + ] + self.wb2csr = csr.WishboneCSRBridge(self.csr.dec.bus, data_width=bus_data_width) + self.csr_bus = self.wb2csr.wb_bus + + def elaborate(self, platform): + m = Module() + + m.submodules += self.csr, self.wb2csr + + # Value right now + value = Signal(self.width) + # When Timer is enabled, start counting + with m.If(self.csr.r.control.f.enable.s): + with m.If(value == 0): + m.d.sync += value.eq(self.csr.r.reload.s) + with m.Else(): + m.d.sync += value.eq(value - 1) + # Otherwise, load Timer value to .value + with m.Else(): + m.d.sync += value.eq(self.csr.r.load.s) + + # If Timer allows updating value in memory, update + m.d.comb += self.csr.r.current.set_val.eq(value) + with m.If(self.csr.r.control.f.update_enable.s): + m.d.sync += self.csr.r.current.set_stb.eq(1) + with m.Else(): + m.d.sync += self.csr.r.current.set_stb.eq(0) + + return m diff --git a/heavycomps/heavycomps/cores/uart.py b/heavycomps/heavycomps/cores/uart.py new file mode 100644 index 0000000..0e0827d --- /dev/null +++ b/heavycomps/heavycomps/cores/uart.py @@ -0,0 +1,90 @@ +from nmigen import * +from nmigen_stdio import serial +from nmigen_soc import csr +from nmigen.utils import * + + +__all__ = ["UARTCore"] + + +class UARTCore(Elaboratable): + def __init__(self, uart_pins, *, sys_clk_freq, bus_data_width, + baudrate=115200, divisor_bits=32, bus_granularity=None): + self.uart_pins = uart_pins + if divisor_bits > 32: + raise NotImplementedError("Currently only supports divisor values " + "not wider than 32 bits") + self.div_bits = divisor_bits + self.div = int(sys_clk_freq/baudrate) - 1 + + bus_dw = bus_data_width + if bus_granularity is None: + bus_granularity = bus_data_width + bus_gr = bus_granularity + + with csr.Bank(name="uart", addr_width=log2_int(4*bus_dw//bus_gr), + data_width=bus_gr) as self.csr: + self.csr.r += [ + csr.Register( + "control", "rw", width=bus_dw, + fields=[csr.Field("divisor", "rw", width=self.div_bits, reset_value=self.div)] + ), + csr.Register( + "flags", "rw", width=bus_dw, + fields=[csr.Field("rx_ready", "r", startbit=0, reset_value=0), + csr.Field("tx_ready", "r", startbit=4, reset_value=1), + csr.Field("rx_enable", "rw", startbit=8)] + ), + csr.Register( + "txdata", "w", width=bus_dw, fields=[csr.Field("value", width=8)] + ), + csr.Register( + "rxdata", "r", width=bus_dw, fields=[csr.Field("value", width=8)] + ) + ] + self.wb2csr = csr.WishboneCSRBridge(self.csr.dec.bus, data_width=bus_data_width) + self.csr_bus = self.wb2csr.wb_bus + + def elaborate(self, platform): + m = Module() + + m.submodules.rxtx = rxtx = serial.AsyncSerial( + divisor=self.div, divisor_bits=self.div_bits, pins=self.uart_pins + ) + rx = rxtx.rx + tx = rxtx.tx + + m.submodules += self.csr, self.wb2csr + + # CSR - Divisor + m.d.comb += rxtx.divisor.eq(self.csr.r.control.f.divisor.s) + + # CSR - RX/TX + tx_ack_now = Signal() + m.d.sync += tx_ack_now.eq(self.csr.r.txdata.bus.w_stb) + + # RX Ready Flag is read-once-and-clear + with m.If(self.csr.r.flags.bus.r_stb): + m.d.comb += [ + self.csr.r.flags.f.rx_ready.set_stb.eq(1), + self.csr.r.flags.f.rx_ready.set_val.eq(0) + ] + with m.Elif(rx.r_rdy): + m.d.comb += [ + self.csr.r.flags.f.rx_ready.set_stb.eq(1), + self.csr.r.flags.f.rx_ready.set_val.eq(1) + ] + + m.d.comb += [ + # TX + self.csr.r.flags.f.tx_ready.set_stb.eq(1), + self.csr.r.flags.f.tx_ready.set_val.eq(~tx.busy), + tx.ack.eq(tx_ack_now), + tx.data.eq(self.csr.r.txdata.f.value.s), + # RX + self.csr.r.rxdata.f.value.set_stb.eq(1), + self.csr.r.rxdata.f.value.set_val.eq(rx.data), + rx.ack.eq(self.csr.r.flags.f.rx_enable.s) + ] + + return m diff --git a/heavycomps/heavycomps/test/test_uart.py b/heavycomps/heavycomps/test/test_uart.py deleted file mode 100644 index fb127e6..0000000 --- a/heavycomps/heavycomps/test/test_uart.py +++ /dev/null @@ -1,41 +0,0 @@ -import unittest - -from nmigen import * -from nmigen.back.pysim import * - -from heavycomps import uart - - -class Loopback(Elaboratable): - def __init__(self, tuning_word=2**31): - self.tx = uart.RS232TX(tuning_word) - self.rx = uart.RS232RX(tuning_word) - - def elaborate(self, platform): - m = Module() - m.submodules.tx = self.tx - m.submodules.rx = self.rx - m.d.comb += self.rx.rx.eq(self.tx.tx) - return m - - -class TestUART(unittest.TestCase): - def test_loopback(self): - dut = Loopback() - test_vector = [32, 129, 201, 39, 0, 255] - - with Simulator(Fragment.get(dut, None)) as sim: - sim.add_clock(1e-6) - - def send(): - for value in test_vector: - yield from dut.tx.write(value) - - def receive(): - for value in test_vector: - received = yield from dut.rx.read() - self.assertEqual(received, value) - - sim.add_sync_process(send) - sim.add_sync_process(receive) - sim.run() diff --git a/heavycomps/heavycomps/uart.py b/heavycomps/heavycomps/uart.py deleted file mode 100644 index d4031d3..0000000 --- a/heavycomps/heavycomps/uart.py +++ /dev/null @@ -1,123 +0,0 @@ -from nmigen import * -from nmigen.lib.cdc import MultiReg - - -__all__ = ["RS232RX", "RS232TX"] - - -class RS232RX(Elaboratable): - def __init__(self, tuning_word): - self.rx = Signal() - self.data = Signal(8) - self.stb = Signal() - self.tuning_word = tuning_word - - def elaborate(self, platform): - m = Module() - - uart_clk_rxen = Signal() - phase_accumulator_rx = Signal(32) - - rx = Signal() - m.submodules += MultiReg(self.rx, rx) - rx_r = Signal() - rx_reg = Signal(8) - rx_bitcount = Signal(4) - rx_busy = Signal() - rx_done = self.stb - rx_data = self.data - m.d.sync += [ - rx_done.eq(0), - rx_r.eq(rx) - ] - with m.If(~rx_busy): - with m.If(~rx & rx_r): # look for start bit - m.d.sync += [ - rx_busy.eq(1), - rx_bitcount.eq(0) - ] - with m.Else(): - with m.If(uart_clk_rxen): - m.d.sync += rx_bitcount.eq(rx_bitcount + 1) - with m.If(rx_bitcount == 0): - with m.If(rx): # verify start bit - m.d.sync += rx_busy.eq(0) - with m.Elif(rx_bitcount == 9): - m.d.sync += rx_busy.eq(0) - with m.If(rx): # verify stop bit - m.d.sync += [ - rx_data.eq(rx_reg), - rx_done.eq(1) - ] - with m.Else(): - m.d.sync += rx_reg.eq(Cat(rx_reg[1:], rx)) - with m.If(rx_busy): - m.d.sync += Cat(phase_accumulator_rx, uart_clk_rxen).eq(phase_accumulator_rx + self.tuning_word) - with m.Else(): - m.d.sync += Cat(phase_accumulator_rx, uart_clk_rxen).eq(2**31) - - return m - - def read(self): - while not (yield self.stb): - yield - value = yield self.data - # clear stb, otherwise multiple calls to this generator keep returning the same value - yield - return value - - -class RS232TX(Elaboratable): - def __init__(self, tuning_word): - self.tx = Signal(reset=1) - self.data = Signal(8) - self.stb = Signal() - self.ack = Signal() - self.tuning_word = tuning_word - - def elaborate(self, platform): - m = Module() - - uart_clk_txen = Signal() - phase_accumulator_tx = Signal(32) - - tx_reg = Signal(8) - tx_bitcount = Signal(4) - tx_busy = Signal() - m.d.sync += self.ack.eq(0) - with m.If(self.stb & ~tx_busy & ~self.ack): - m.d.sync += [ - tx_reg.eq(self.data), - tx_bitcount.eq(0), - tx_busy.eq(1), - self.tx.eq(0) - ] - with m.Elif(uart_clk_txen & tx_busy): - m.d.sync += tx_bitcount.eq(tx_bitcount + 1) - with m.If(tx_bitcount == 8): - m.d.sync += self.tx.eq(1) - with m.Elif(tx_bitcount == 9): - m.d.sync += [ - self.tx.eq(1), - tx_busy.eq(0), - self.ack.eq(1) - ] - with m.Else(): - m.d.sync += [ - self.tx.eq(tx_reg[0]), - tx_reg.eq(Cat(tx_reg[1:], 0)) - ] - with m.If(tx_busy): - m.d.sync += Cat(phase_accumulator_tx, uart_clk_txen).eq(phase_accumulator_tx + self.tuning_word) - with m.Else(): - m.d.sync += Cat(phase_accumulator_tx, uart_clk_txen).eq(0) - - return m - - def write(self, data): - yield self.stb.eq(1) - yield self.data.eq(data) - yield - while not (yield self.ack): - yield - yield self.stb.eq(0) diff --git a/heavycomps/heavycomps/wishbone.py b/heavycomps/heavycomps/wishbone.py deleted file mode 100644 index fb3f875..0000000 --- a/heavycomps/heavycomps/wishbone.py +++ /dev/null @@ -1,210 +0,0 @@ -from enum import Enum -from functools import reduce -from operator import or_ - -from nmigen import * -from nmigen.hdl.rec import * - - -__all__ = ["Cycle", "Interface", "Arbiter", "Decoder", "InterconnectShared"] - - -class Cycle(Enum): - CLASSIC = 0 - CONSTANT = 1 - INCREMENT = 2 - END = 7 - - -wishbone_layout = [ - ("adr", 30, DIR_FANOUT), - ("dat_w", 32, DIR_FANOUT), - ("dat_r", 32, DIR_FANIN), - ("sel", 4, DIR_FANOUT), - ("cyc", 1, DIR_FANOUT), - ("stb", 1, DIR_FANOUT), - ("ack", 1, DIR_FANIN), - ("we", 1, DIR_FANOUT), - ("cti", 3, DIR_FANOUT), - ("bte", 2, DIR_FANOUT), - ("err", 1, DIR_FANIN) -] - - -class Interface(Record): - def __init__(self): - Record.__init__(self, wishbone_layout) - - def _do_transaction(self): - yield self.cyc.eq(1) - yield self.stb.eq(1) - yield - while not (yield self.ack): - yield - yield self.cyc.eq(0) - yield self.stb.eq(0) - - def write(self, adr, dat, sel=None): - if sel is None: - sel = 2**len(self.sel) - 1 - yield self.adr.eq(adr) - yield self.dat_w.eq(dat) - yield self.sel.eq(sel) - yield self.we.eq(1) - yield from self._do_transaction() - - def read(self, adr): - yield self.adr.eq(adr) - yield self.we.eq(0) - yield from self._do_transaction() - return (yield self.dat_r) - - -class SRAM(Elaboratable): - def __init__(self, mem, read_only=False, bus=None): - self.mem = mem - self.read_only = read_only - if bus is None: - bus = Interface() - self.bus = bus - - def elaborate(self, platform): - m = Module() - - if self.mem.width > len(self.bus.dat_r): - raise NotImplementedError - - # read - m.submodules.rdport = rdport = self.mem.read_port() - m.d.comb += [ - rdport.addr.eq(self.bus.adr[:len(rdport.addr)]), - self.bus.dat_r.eq(rdport.data) - ] - - # write - if not self.read_only: - m.submodules.wrport = wrport = self.mem.write_port(granularity=8) - m.d.comb += [ - wrport.addr.eq(self.bus.adr[:len(rdport.addr)]), - wrport.data.eq(self.bus.dat_w) - ] - for i in range(4): - m.d.comb += wrport.en[i].eq(self.bus.cyc & self.bus.stb & self.bus.we & self.bus.sel[i]) - - # generate ack - m.d.sync += self.bus.ack.eq(0) - with m.If(self.bus.cyc & self.bus.stb & ~self.bus.ack): - m.d.sync += self.bus.ack.eq(1) - - return m - - -class RoundRobin(Elaboratable): - def __init__(self, n): - self.n = n - self.request = Signal(n) - self.grant = Signal(max=n) - - def elaborate(self, platform): - m = Module() - with m.Switch(self.grant): - for i in range(self.n): - with m.Case(i): - with m.If(~self.request[i]): - for j in reversed(range(i+1, i+self.n)): - t = j % self.n - with m.If(self.request[t]): - m.d.sync += self.grant.eq(t) - return m - - -class Arbiter(Elaboratable): - def __init__(self, masters, target): - self.masters = masters - self.target = target - - def elaborate(self, platform): - m = Module() - - m.submodules.rr = rr = RoundRobin(len(self.masters)) - - # mux master->target signals - for name, size, direction in wishbone_layout: - if direction == DIR_FANOUT: - choices = Array(getattr(m, name) for m in self.masters) - m.d.comb += getattr(self.target, name).eq(choices[rr.grant]) - - # connect target->master signals - for name, size, direction in wishbone_layout: - if direction == DIR_FANIN: - source = getattr(self.target, name) - for i, master in enumerate(self.masters): - dest = getattr(master, name) - if name == "ack" or name == "err": - m.d.comb += dest.eq(source & (rr.grant == i)) - else: - m.d.comb += dest.eq(source) - - # connect bus requests to round-robin selector - reqs = [m.cyc & ~m.ack for m in self.masters] - m.d.comb += rr.request.eq(Cat(*reqs)) - - return m - - -class Decoder(Elaboratable): - def __init__(self, master, targets, register=False): - self.master = master - self.targets = targets - self.register = register - - def elaborate(self, platform): - m = Module() - - nt = len(self.targets) - target_sel = Signal(nt) - target_sel_r = Signal(nt) - - # decode target addresses - for i, (fun, bus) in enumerate(self.targets): - m.d.comb += target_sel[i].eq(fun(self.master.adr)) - if self.register: - m.d.sync += target_sel_r.eq(target_sel) - else: - m.d.comb += target_sel_r.eq(target_sel) - - # connect master->targets signals except cyc - for target in self.targets: - for name, size, direction in wishbone_layout: - if direction == DIR_FANOUT and name != "cyc": - m.d.comb += getattr(target[1], name).eq(getattr(self.master, name)) - - # combine cyc with target selection signals - for i, target in enumerate(self.targets): - m.d.comb += target[1].cyc.eq(self.master.cyc & target_sel[i]) - - # generate master ack (resp. err) by ORing all target acks (resp. errs) - m.d.comb += [ - self.master.ack.eq(reduce(or_, [target[1].ack for target in self.targets])), - self.master.err.eq(reduce(or_, [target[1].err for target in self.targets])) - ] - - # mux (1-hot) target data return - masked = [Repl(target_sel_r[i], len(self.master.dat_r)) & self.targets[i][1].dat_r for i in range(nt)] - m.d.comb += self.master.dat_r.eq(reduce(or_, masked)) - - return m - - -class InterconnectShared(Module): - def __init__(self, masters, targets, register=False): - self.masters = masters - self.targets = targets - self.register = register - - def elaborate(self, platform): - m = Module() - shared = Interface() - m.submodules.arbiter = Arbiter(self.masters, shared) - m.submodules.decoder = Decoder(shared, self.targets, self.register) - return m diff --git a/heavycomps/setup.py b/heavycomps/setup.py index 63af452..f2dacab 100644 --- a/heavycomps/setup.py +++ b/heavycomps/setup.py @@ -2,6 +2,6 @@ from setuptools import setup, find_packages setup( name="heavycomps", - install_requires=["nmigen"], + install_requires=["nmigen", "nmigen_stdio", "nmigen_soc"], packages=find_packages() ) diff --git a/release.nix b/release.nix index b4bed21..8e08a93 100644 --- a/release.nix +++ b/release.nix @@ -8,6 +8,8 @@ let helloworld_ecp5 = import ./examples/helloworld_ecp5.nix { inherit pkgs hx; }; helloworld_kintex7 = import ./examples/helloworld_kintex7.nix { inherit pkgs hx; }; simplesoc_ecp5 = import ./examples/simplesoc_ecp5.nix { inherit pkgs hx; }; + + testing_ecp5 = import ./examples/testing_ecp5.nix { inherit pkgs hx; }; }; in builtins.mapAttrs (name: value: pkgs.lib.hydraJob value) jobs