Browse Source

implement UART, Timer, SPI Flash & Eth RGMII cores

* These implementations use Harry's proposal for nmigen-stdio & nmigen-soc
core_lib
Harry Ho 2 years ago
parent
commit
353b34a135
  1. 6
      default.nix
  2. 13
      eda/nmigen-boards.nix
  3. 25
      eda/nmigen-soc.nix
  4. 25
      eda/nmigen-stdio.nix
  5. 58
      examples/helloworld_ecp5.py
  6. 9
      examples/helloworld_kintex7.nix
  7. 53
      examples/helloworld_kintex7.py
  8. 11
      examples/simplesoc_ecp5.nix
  9. 99
      examples/simplesoc_ecp5.py
  10. 7
      examples/testing_ecp5.nix
  11. 97
      examples/testing_ecp5.py
  12. 5
      firmware/memory.x
  13. 21
      firmware/src/main.rs
  14. 0
      firmware/testing/Cargo.lock
  15. 0
      firmware/testing/Cargo.toml
  16. 0
      firmware/testing/build.rs
  17. 2
      firmware/testing/default.nix
  18. 5
      firmware/testing/memory.x
  19. 45
      firmware/testing/src/eth.rs
  20. 27
      firmware/testing/src/gpio.rs
  21. 310
      firmware/testing/src/main.rs
  22. 22
      firmware/testing/src/spiflash.rs
  23. 30
      firmware/testing/src/timer.rs
  24. 78
      firmware/testing/src/uart.rs
  25. 4
      heavycomps.nix
  26. 0
      heavycomps/heavycomps/cfgs/__init__.py
  27. 106
      heavycomps/heavycomps/cfgs/ecp5.py
  28. 5
      heavycomps/heavycomps/cores/__init__.py
  29. 159
      heavycomps/heavycomps/cores/eth_rgmii.py
  30. 43
      heavycomps/heavycomps/cores/gpio.py
  31. 94
      heavycomps/heavycomps/cores/spiflash.py
  32. 68
      heavycomps/heavycomps/cores/timer.py
  33. 90
      heavycomps/heavycomps/cores/uart.py
  34. 41
      heavycomps/heavycomps/test/test_uart.py
  35. 123
      heavycomps/heavycomps/uart.py
  36. 210
      heavycomps/heavycomps/wishbone.py
  37. 2
      heavycomps/setup.py
  38. 2
      release.nix

6
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; };
}

13
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;
};

25
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 ];
};
}

25
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 ];
};
}

58
examples/helloworld_ecp5.py

@ -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()

9
examples/helloworld_kintex7.nix

@ -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
''

53
examples/helloworld_kintex7.py

@ -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()

11
examples/simplesoc_ecp5.nix

@ -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
''

99
examples/simplesoc_ecp5.py

@ -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("<I", word)[0])
return firmware
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--simulate", action="store_true")
parser.add_argument("firmware_bin")
parser.add_argument("build_dir")
args = parser.parse_args()
firmware = read_firmware(args.firmware_bin)
top = Top(firmware, args.simulate)
if args.simulate:
os.makedirs(args.build_dir, exist_ok=True)
with pysim.Simulator(top,
vcd_file=open(os.path.join(args.build_dir, "simplesoc.vcd"), "w"),
gtkw_file=open(os.path.join(args.build_dir, "simplesoc.gtkw"), "w")) as sim:
sim.add_clock(1e-6)
sim.run_until(1000e-6, run_passive=True)
else:
VersaECP5Platform().build(top, build_dir=args.build_dir)
if __name__ == "__main__":
main()

7
examples/helloworld_ecp5.nix → examples/testing_ecp5.nix

@ -1,11 +1,12 @@
{ pkgs, hx }:
pkgs.runCommand "helloworld-bitstream" {
buildInputs = [ (pkgs.python3.withPackages(ps: [hx.nmigen hx.nmigen-boards hx.heavycomps])) pkgs.yosys ];
pkgs.runCommand "testing-ecp5-bitstream" {
buildInputs = [ (pkgs.python3.withPackages(ps: [hx.nmigen hx.nmigen-boards hx.nmigen-soc 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 ${./helloworld_ecp5.py} $out
#export NMIGEN_verbose=1
python ${./testing_ecp5.py} ${hx.fw-helloworld}/testing.bin $out
''

97
examples/testing_ecp5.py

@ -0,0 +1,97 @@
import argparse
import struct
from nmigen import *
from nmigen_boards.versa_ecp5 import VersaECP5Platform_USRMCLK
from heavycomps.cores import *
from heavycomps.cfgs.ecp5 import *
from minerva.core import Minerva
from nmigen_soc import wishbone
from nmigen.utils import *
class Top(Elaboratable):
def __init__(self, firmware):
self.firmware = firmware
def elaborate(self, platform):
m = Module()
# Referring to simplesoc_ecp5.py
cd_sync = ClockDomain()
m.domains += cd_sync
m.d.comb += cd_sync.clk.eq(platform.request("clk100").i)
io_user_led = [platform.request("led", i).o for i in range(8)]
io_uart = platform.request("uart")
io_spiflash = platform.request("spi_flash_1x")
io_eth = platform.request("eth_rgmii", number=0)
# FIXME: Without an sync stmt, clk100 must be checked...
dummy = Signal()
m.d.sync += dummy.eq(0)
m.submodules.cpu = cpu = Minerva(with_icache=False, with_dcache=False, with_muldiv=False)
m.submodules.ram = ram = wishbone.SRAM(Memory(width=32, depth=2**15, init=self.firmware),
granularity=8, features={"err","cti","bte"})
m.submodules.timer = timer = TimerCore(width=32,
bus_data_width=32, bus_granularity=8)
m.submodules.led = led = GPIOOutput(io_user_led, count=8,
bus_data_width=32, bus_granularity=8)
m.submodules.uart = uart = UARTCore(io_uart, sys_clk_freq=100e6,
bus_data_width=32, bus_granularity=8)
m.submodules.spi = spi = SPIFlashCore(io_spiflash, spi_protocol="standard", read_type="slow",
addr_width=24, sys_clk_freq=100e6, freq=10e6,
data_width=32, granularity=8)
m.submodules += SPIFlashCoreCfg(spi)
m.submodules.eth = eth = EthRGMIICore(rx=True, tx=False,
bus_data_width=32, bus_granularity=8)
m.submodules += EthRGMIICoreCfg(eth, io_eth, rx=True, tx=False)
m.submodules.con = con = wishbone.InterconnectShared(
addr_width=30, data_width=32, granularity=8,
features={"err","cti","bte"},
itors=[cpu.ibus, cpu.dbus],
targets=[
( ram.bus, 0x00000000 ),
( uart.csr_bus, 0x02000000 ),
( timer.csr_bus, 0x02000020 ),
( led.csr_bus, 0x02000010 ),
( spi.bus, 0x03000000 ),
( spi.csr_bus, 0x02000100 ),
( eth.csr_bus, 0x02000200 ),
( eth.rxdat_bus, 0x02000204 )
]
)
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("<I", word)[0])
return firmware
def main():
parser = argparse.ArgumentParser()
parser.add_argument("firmware_bin")
parser.add_argument("build_dir")
args = parser.parse_args()
firmware = read_firmware(args.firmware_bin)
top = Top(firmware)
VersaECP5Platform_USRMCLK().build(top, build_dir=args.build_dir)
if __name__ == "__main__":
main()

5
firmware/memory.x

@ -1,5 +0,0 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 16K
RAM : ORIGIN = 0x0004000, LENGTH = 16K
}

21
firmware/src/main.rs

@ -1,21 +0,0 @@
#![no_std]
#![no_main]
extern crate riscv_rt;
extern crate panic_halt;
use riscv_rt::entry;
fn print(string: &str) {
for c in string.chars() {
let mem = 0x00400000 as *mut u8;
unsafe { *mem = c as u8 }
}
}
#[entry]
fn main() -> ! {
loop {
print("hello2");
}
}

0
firmware/Cargo.lock → firmware/testing/Cargo.lock

0
firmware/Cargo.toml → firmware/testing/Cargo.toml

0
firmware/build.rs → firmware/testing/build.rs

2
firmware/default.nix → 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
'';
}

5
firmware/testing/memory.x

@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 64K
RAM : ORIGIN = 0x00004000, LENGTH = 16K
}

45
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;
}
}

27
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);
}
}

310
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);
}
}

22
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
}
}

30
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
}
}

78
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)*));
}

4
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";

0
heavycomps/heavycomps/cfgs/__init__.py

106
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

5
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 *

159
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

43
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.