Compare commits

..

3 Commits

Author SHA1 Message Date
harry f7f933b351 update README 2020-04-30 16:47:30 +08:00
Harry Ho b872a72866 fix GPIO CSR issue; add "invert" option 2020-04-29 12:51:15 +08:00
Harry Ho 353b34a135 implement UART, Timer, SPI Flash & Eth RGMII cores
* These implementations use Harry's proposal for nmigen-stdio & nmigen-soc
2020-03-02 19:55:22 +08:00
39 changed files with 1255 additions and 647 deletions

View File

@ -3,9 +3,7 @@ HeavyX
A FPGA SoC framework embracing cutting-edge open source technologies (nMigen, Yosys, SymbiFlow, Minerva, Nix, Rust). A FPGA SoC framework embracing cutting-edge open source technologies (nMigen, Yosys, SymbiFlow, Minerva, Nix, Rust).
This is work in progress! SoC demo
"Hello World" SoC demo
---------------------- ----------------------
Softcore system-on-chip on the Lattice ECP5 Versa board, built with a 100% Verilog/VHDL-free and 100% open source toolchain. Softcore system-on-chip on the Lattice ECP5 Versa board, built with a 100% Verilog/VHDL-free and 100% open source toolchain.
@ -13,7 +11,7 @@ Softcore system-on-chip on the Lattice ECP5 Versa board, built with a 100% Veril
* Everything written in nMigen (https://github.com/m-labs/nmigen/). * Everything written in nMigen (https://github.com/m-labs/nmigen/).
* RISC-V 32-bit pipelined core (Minerva by Lambdaconcept). * RISC-V 32-bit pipelined core (Minerva by Lambdaconcept).
* 100MHz clock frequency. * 100MHz clock frequency.
* Runs a Rust "hello world" program. * Runs a Rust program controlling the UART, SPI flash, Ethernet and GPIO.
Use nixpkgs 19.03. If you are unfamiliar with Nix and just installed it on another (non-NixOS) distribution, simply run: Use nixpkgs 19.03. If you are unfamiliar with Nix and just installed it on another (non-NixOS) distribution, simply run:
@ -32,7 +30,7 @@ substituters = https://cache.nixos.org https://nixbld.m-labs.hk
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc= trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc=
``` ```
Run ``nix-build -A simplesoc_ecp5 release.nix`` Run ``nix-build -A testing_ecp5 release.nix``
You can also build manually and use your distribution's packages, but YMMV. You can also build manually and use your distribution's packages, but YMMV.

View File

@ -3,6 +3,8 @@ rec {
vivado = import ./eda/vivado.nix { inherit pkgs; }; vivado = import ./eda/vivado.nix { inherit pkgs; };
nmigen = pkgs.callPackage ./eda/nmigen.nix { }; nmigen = pkgs.callPackage ./eda/nmigen.nix { };
nmigen-boards = pkgs.callPackage ./eda/nmigen-boards.nix { inherit nmigen; }; 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 {}; scala-spinalhdl = pkgs.callPackage ./eda/scala-spinalhdl.nix {};
jtagtap = pkgs.callPackage ./cores/jtagtap.nix { inherit nmigen; }; jtagtap = pkgs.callPackage ./cores/jtagtap.nix { inherit nmigen; };
@ -15,7 +17,7 @@ rec {
litex = pkgs.callPackage ./cores/litex.nix { inherit nmigen; }; litex = pkgs.callPackage ./cores/litex.nix { inherit nmigen; };
litedram = pkgs.callPackage ./cores/litedram.nix { inherit litex; }; 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"; }; binutils-riscv32 = pkgs.callPackage ./compilers/binutils.nix { platform = "riscv32"; };
gcc-riscv32 = pkgs.callPackage ./compilers/gcc.nix { platform = "riscv32"; platform-binutils = binutils-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; }; gcc-riscv64 = pkgs.callPackage ./compilers/gcc.nix { platform = "riscv64"; platform-binutils = binutils-riscv64; };
rust-riscv32i-crates = pkgs.callPackage ./compilers/rust-riscv32i-crates.nix { }; 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; };
} }

View File

@ -5,9 +5,16 @@ python3Packages.buildPythonPackage {
version = "2019-10-13"; version = "2019-10-13";
src = fetchgit { src = fetchgit {
url = "https://github.com/m-labs/nmigen-boards"; # Using official master branch
rev = "bc074915d6c67a33447d4730b720b37e3ea4bb3f"; #url = "https://github.com/m-labs/nmigen-boards";
sha256 = "1qp32783p0bg6fxfj0wd7fd3if8az7w1r521wcbznxakdrqhrqjd"; #rev = "bc074915d6c67a33447d4730b720b37e3ea4bb3f";
#sha256 = "1qp32783p0bg6fxfj0wd7fd3if8az7w1r521wcbznxakdrqhrqjd";
# Using harry's fork
url = "https://github.com/HarryHo90sHK/nmigen-boards";
rev = "d51e1d5f53aca59d138860d332edf84fdce7cfa3";
sha256 = "0yfs81bhcx4pm4q83pjhiibc1zknsllimqb2c753r3vvbcg4frmw";
leaveDotGit = true; leaveDotGit = true;
}; };

25
eda/nmigen-soc.nix Normal file
View File

@ -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 Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,12 @@
{ pkgs, hx }: { pkgs, hx }:
pkgs.runCommand "helloworld-bitstream" { pkgs.runCommand "testing-ecp5-bitstream" {
buildInputs = [ (pkgs.python3.withPackages(ps: [hx.nmigen hx.nmigen-boards hx.heavycomps])) pkgs.yosys ]; 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 YOSYS=${pkgs.yosys}/bin/yosys
export NEXTPNR_ECP5=${pkgs.nextpnr}/bin/nextpnr-ecp5 export NEXTPNR_ECP5=${pkgs.nextpnr}/bin/nextpnr-ecp5
export ECPPACK=${pkgs.trellis}/bin/ecppack 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 Normal file
View File

@ -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, invert=True)
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()

View File

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

View File

@ -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");
}
}

View File

@ -19,6 +19,6 @@ rustPlatform.buildRustPackage rec {
installPhase = '' installPhase = ''
mkdir -p $out mkdir -p $out
cp target/riscv32i-unknown-none-elf/release/helloworld $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
''; '';
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
{ stdenv, python3Packages, nmigen }: { stdenv, python3Packages, nmigen, nmigen-stdio, nmigen-soc }:
python3Packages.buildPythonPackage { python3Packages.buildPythonPackage {
name = "heavycomps"; name = "heavycomps";
src = ./heavycomps; src = ./heavycomps;
propagatedBuildInputs = [ nmigen ]; propagatedBuildInputs = [ nmigen nmigen-stdio nmigen-soc ];
meta = with stdenv.lib; { meta = with stdenv.lib; {
description = "Components for the HeavyX SoC toolkit"; description = "Components for the HeavyX SoC toolkit";

View File

View File

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

View File

@ -0,0 +1,5 @@
from .uart import *
from .gpio import *
from .timer import *
from .spiflash import *
from .eth_rgmii import *

View File

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

View File

@ -0,0 +1,42 @@
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, invert=False):
self.gpio_out = gpio_out
self.count = count
self.invert = 1 if invert else 0
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)) + 1,
data_width=bus_gr, type="mux") as self.csr:
self.csr.r += csr.Register("switch", "rw", width=count)
with self.csr.r.switch as reg:
reg.f += [
csr.Field("output_{}".format(i), reset_value=self.invert)
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] ^ self.invert)
for i in range(self.count)
]
return m

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,6 @@ from setuptools import setup, find_packages
setup( setup(
name="heavycomps", name="heavycomps",
install_requires=["nmigen"], install_requires=["nmigen", "nmigen_stdio", "nmigen_soc"],
packages=find_packages() packages=find_packages()
) )

View File

@ -8,6 +8,8 @@ let
helloworld_ecp5 = import ./examples/helloworld_ecp5.nix { inherit pkgs hx; }; helloworld_ecp5 = import ./examples/helloworld_ecp5.nix { inherit pkgs hx; };
helloworld_kintex7 = import ./examples/helloworld_kintex7.nix { inherit pkgs hx; }; helloworld_kintex7 = import ./examples/helloworld_kintex7.nix { inherit pkgs hx; };
simplesoc_ecp5 = import ./examples/simplesoc_ecp5.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 in
builtins.mapAttrs (name: value: pkgs.lib.hydraJob value) jobs builtins.mapAttrs (name: value: pkgs.lib.hydraJob value) jobs