Compare commits

...

9 Commits

Author SHA1 Message Date
05fed9e28e remove extra submodule I added for testing 2023-04-22 18:41:12 -06:00
5cfc562190 fixup fw 2023-04-22 18:39:59 -06:00
9dec411ff0 hw: Add a few more HW bugs found to the README 2023-04-22 18:39:40 -06:00
35a8841aa5 fw: update some drivers for LiteX
Still need to re-do some stuff and clean up, but it runs on LiteX now
2023-04-22 18:39:01 -06:00
c5db01c70f gw: fix eth.py addressing issues 2023-04-22 18:38:26 -06:00
c0b293e0c7 gateware: move back to LiteX
Had too many issues with integrating LiteEth. I put my FW into a LiteX
SoC and it worked, so I migrated back. With the knowledge I gained doing
Amaranth I could fix the issues I had adding a wishbone slave device
pretty easily.
2023-04-22 18:35:13 -06:00
833db12d58 fw: make build script return with error code so it can be scripted 2023-04-22 17:41:51 -06:00
a864da5354 fw: eth testing and debug process 2023-04-22 13:39:32 -06:00
29ec5a8a43 tracking a couple files I missed 2023-04-16 11:06:02 -06:00
14 changed files with 750 additions and 96 deletions

1
firmware/Cargo.lock generated
View File

@ -105,6 +105,7 @@ dependencies = [
"defmt",
"embedded-hal",
"panic-halt",
"riscv",
"riscv-rt",
"smoltcp",
]

View File

@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
riscv-rt = "0.11.0"
riscv = "0.10.1"
panic-halt = "0.2.0"
embedded-hal = "0.2.7"
defmt = {version = "0.3.4", features = ["encoding-raw"] }

View File

@ -1,4 +1,9 @@
#!/usr/bin/sh
DEFMT_LOG=trace cargo build --release
if [ $? -ne 0 ]
then
exit $?
fi
riscv64-unknown-elf-objcopy -S -O binary target/riscv32i-unknown-none-elf/release/fw fw.bin

View File

@ -1,7 +1,7 @@
MEMORY
{
RAM : ORIGIN = 0x01100000, LENGTH = 4K
FLASH : ORIGIN = 0x01000000, LENGTH = 64K
RAM : ORIGIN = 0x10000000, LENGTH = 64K
FLASH : ORIGIN = 0x00000000, LENGTH = 64K
}
REGION_ALIAS("REGION_TEXT", FLASH);

View File

@ -14,26 +14,22 @@
// - Slots are sized to ethernet MTU (1530), and addressed by the closest log2
// thing, so 2048 bytes each
const LITEETH_BASE: u32 = 0x0300_0000;
const CTRL_RESET: u32 = 0x000;
const CTRL_SCRATCH: u32 = 0x004;
// Writer, or RX register blocks
const ETHMAC_SRAM_WRITER_SLOT: u32 = 0x800;
const ETHMAC_SRAM_WRITER_LENGTH: u32 = 0x804;
const ETHMAC_SRAM_WRITER_EV_STATUS: u32 = 0x80c;
const ETHMAC_SRAM_WRITER_EV_PENDING: u32 = 0x810;
const ETHMAC_SRAM_WRITER_EV_ENABLE: u32 = 0x814;
const ETHMAC_SRAM_WRITER_SLOT: u32 = 0x000;
const ETHMAC_SRAM_WRITER_LENGTH: u32 = 0x004;
const ETHMAC_SRAM_WRITER_EV_STATUS: u32 = 0x00c;
const ETHMAC_SRAM_WRITER_EV_PENDING: u32 = 0x010;
const ETHMAC_SRAM_WRITER_EV_ENABLE: u32 = 0x014;
// Reader, or TX register blocks
const ETHMAC_SRAM_READER_START: u32 = 0x818;
const ETHMAC_SRAM_READER_READY: u32 = 0x81c;
const ETHMAC_SRAM_READER_SLOT: u32 = 0x824;
const ETHMAC_SRAM_READER_LENGTH: u32 = 0x828;
const ETHMAC_SRAM_READER_EV_STATUS: u32 = 0x82c;
const ETHMAC_SRAM_READER_EV_PENDING: u32 = 0x830;
const ETHMAC_SRAM_READER_EV_ENABLE: u32 = 0x834;
const ETHMAC_SRAM_READER_START: u32 = 0x018;
const ETHMAC_SRAM_READER_READY: u32 = 0x01c;
const ETHMAC_SRAM_READER_LEVEL: u32 = 0x020;
const ETHMAC_SRAM_READER_SLOT: u32 = 0x024;
const ETHMAC_SRAM_READER_LENGTH: u32 = 0x028;
const ETHMAC_SRAM_READER_EV_STATUS: u32 = 0x02c;
const ETHMAC_SRAM_READER_EV_PENDING: u32 = 0x030;
const ETHMAC_SRAM_READER_EV_ENABLE: u32 = 0x014;
const NUM_RX_SLOTS: u32 = 2;
const NUM_TX_SLOTS: u32 = 2;
@ -46,52 +42,37 @@ use crate::uart::AmlibUart;
use core::fmt::Write;
pub struct LiteEthDevice {
base_addr: u32,
csr_addr: u32,
ethmac_addr: u32,
}
pub struct LiteEthTxToken {
pub base_addr: u32,
pub csr_addr: u32,
pub ethmac_addr: u32,
pub slot: u32,
}
pub struct LiteEthRxToken {
pub base_addr: u32,
pub csr_addr: u32,
pub ethmac_addr: u32,
}
impl LiteEthDevice {
/// Initialises the device and returns an instance. Unsafe because there are
/// no checks for other users.
pub unsafe fn try_init(base_addr: u32) -> Option<Self> {
if !LiteEthDevice::check_wishbone_access(base_addr) {
return None;
}
// Reset liteeth
write_reg(base_addr + CTRL_RESET, 1u32);
busy_wait(200);
write_reg(base_addr + CTRL_RESET, 0u32);
busy_wait(200);
pub unsafe fn try_init(csr_addr: u32, ethmac_addr: u32) -> Option<Self> {
// Clear RX event to mark the slot as available
write_reg(base_addr + ETHMAC_SRAM_WRITER_EV_PENDING, 1u32);
write_reg(csr_addr + ETHMAC_SRAM_WRITER_EV_PENDING, 1u32);
// Clear TX event (unsure if necessary)
write_reg(base_addr + ETHMAC_SRAM_READER_EV_PENDING, 1u32);
write_reg(csr_addr + ETHMAC_SRAM_READER_EV_PENDING, 1u32);
// Disable event interrupts, we poll, so no use for an interrupt
write_reg(base_addr + ETHMAC_SRAM_READER_EV_ENABLE, 0u32);
write_reg(base_addr + ETHMAC_SRAM_WRITER_EV_ENABLE, 0u32);
write_reg(csr_addr + ETHMAC_SRAM_READER_EV_ENABLE, 0u32);
write_reg(csr_addr + ETHMAC_SRAM_WRITER_EV_ENABLE, 0u32);
// Return a new device
Some(Self { base_addr})
}
/// Checks that wishbone memory access is correct for the given base address
unsafe fn check_wishbone_access(base_addr: u32) -> bool {
// Read scratch register, which resets to 0x12345678
let value: u32 = read_reg(base_addr + CTRL_SCRATCH);
// If this isn't true, we screwed.
return value == 0x12345678;
Some(Self { csr_addr, ethmac_addr })
}
}
@ -107,13 +88,13 @@ impl smoltcp::phy::Device for LiteEthDevice {
unsafe {
// No data is available
if read_reg::<u32>(self.base_addr + ETHMAC_SRAM_WRITER_EV_STATUS) == 0 {
if read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_EV_STATUS) == 0 {
return None;
}
// Check if TX slot 1 is available for the "return" packet
write_reg(self.base_addr + ETHMAC_SRAM_READER_SLOT, 1u32);
if read_reg::<u32>(self.base_addr + ETHMAC_SRAM_READER_READY) != 1 {
// Due to the fact that I can't check the status of an individual slot, I am going to just make sure
// level is 0 before I hand out any TX tokens
if read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_READER_LEVEL) != 0 {
return None;
}
@ -123,10 +104,12 @@ impl smoltcp::phy::Device for LiteEthDevice {
defmt::trace!("RX Token given");
Some((
LiteEthRxToken {
base_addr: self.base_addr,
csr_addr: self.csr_addr,
ethmac_addr: self.ethmac_addr,
},
LiteEthTxToken {
base_addr: self.base_addr,
csr_addr: self.csr_addr,
ethmac_addr: self.ethmac_addr,
slot: 1,
},
))
@ -136,15 +119,17 @@ impl smoltcp::phy::Device for LiteEthDevice {
fn transmit(&mut self, _timestamp: smoltcp::time::Instant) -> Option<Self::TxToken<'_>> {
// Check if slot 0 is ready, if so, return TxToken to slot 0
unsafe {
write_reg(self.base_addr + ETHMAC_SRAM_READER_SLOT, 0u32);
if read_reg::<u32>(self.base_addr + ETHMAC_SRAM_READER_READY) == 0 {
// Due to the fact that I can't check the status of an individual slot, I am going to just make sure
// level is 0 before I hand out any TX tokens
if read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_READER_LEVEL) != 0 {
return None;
}
}
//writeln!(self.uart, "TX tkn").unwrap();
defmt::trace!("TX token given");
Some(LiteEthTxToken {
base_addr: self.base_addr,
csr_addr: self.csr_addr,
ethmac_addr: self.ethmac_addr,
slot: 0,
})
}
@ -167,23 +152,41 @@ impl smoltcp::phy::TxToken for LiteEthTxToken {
where
F: FnOnce(&mut [u8]) -> R,
{
// TODO 0x800 is ETHMAC offset, need to encode it somehow properly
let tx_slot_base: u32 = self.base_addr + 0x800 + NUM_RX_SLOTS * SLOT_LEN;
let tx_slot_addr = tx_slot_base + (self.slot as u32) * SLOT_LEN;
let tx_slot_base: u32 = self.ethmac_addr + NUM_RX_SLOTS * SLOT_LEN;
let tx_slot_addr = tx_slot_base + 0 * SLOT_LEN;
let tx_slot: &mut [u8] =
unsafe { core::slice::from_raw_parts_mut(tx_slot_addr as *mut u8, MTU) };
unsafe { core::slice::from_raw_parts_mut(tx_slot_addr as *mut u8, len) };
unsafe {
// Wait for it to be ready?
while read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_READER_READY) == 0 {}
}
// Write data to buffer
let res = f(tx_slot);
// Write length, and start sending data
unsafe {
let level = read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_READER_LEVEL);
defmt::trace!("level before sending: {}", level);
// set slot
write_reg(self.base_addr + ETHMAC_SRAM_READER_SLOT, self.slot);
write_reg(self.csr_addr + ETHMAC_SRAM_READER_SLOT, 0u32);
// set length
write_reg(self.base_addr + ETHMAC_SRAM_READER_LENGTH, len as u32);
write_reg(self.csr_addr + ETHMAC_SRAM_READER_LENGTH, len as u32);
let slot = read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_READER_SLOT);
let length = read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_READER_LENGTH);
defmt::trace!("slot: {}, len: {}, addr: 0x{:08x}", slot, len, tx_slot_addr);
// send data
write_reg(self.base_addr + ETHMAC_SRAM_READER_START, 1u32);
write_reg(self.csr_addr + ETHMAC_SRAM_READER_START, 1u32);
let level = read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_READER_LEVEL);
defmt::trace!("level after sending: {}", level);
// Clear event because why tf not
write_reg(self.csr_addr + ETHMAC_SRAM_READER_EV_PENDING, 1u32);
}
res
@ -197,34 +200,24 @@ impl smoltcp::phy::RxToken for LiteEthRxToken {
{
// Read the slot number
let slot = unsafe {
read_reg::<u32>(self.base_addr + ETHMAC_SRAM_WRITER_SLOT)
read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_SLOT)
};
// Read the available length
let len = unsafe {
read_reg::<u32>(self.base_addr + ETHMAC_SRAM_WRITER_LENGTH)
read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_LENGTH)
};
// TODO 0x800 is ETHMAC offset, need to encode it somehow properly
let rx_slot_base: u32 = self.base_addr + 0x800 + SLOT_LEN;
let rx_slot_addr: u32 = rx_slot_base + slot * SLOT_LEN;
let rx_slot_addr: u32 = self.ethmac_addr + slot * SLOT_LEN;
let rx_slot: &mut [u8] =
unsafe { core::slice::from_raw_parts_mut(rx_slot_addr as *mut u8, len as usize) };
defmt::trace!("RX packet data. slot: {}, len: {}, addr: 0x{:08x}", slot, len, rx_slot_addr);
for i in 0..16 {
let base = self.base_addr + i * 0x400;
defmt::trace!("Data at offset: 0x{:08x}", base);
for j in 0..32 {
defmt::trace!("byte {}: 0x{:x}", j, unsafe {read_reg::<u8>(base + j)});
}
}
defmt::trace!("rx: len {}, addr: 0x{:08x}", len, rx_slot_addr);
// Read data from buffer
let res = f(rx_slot);
// Clear event to mark slot as available
unsafe {
write_reg(self.base_addr + ETHMAC_SRAM_WRITER_EV_PENDING, 1u32);
write_reg(self.csr_addr + ETHMAC_SRAM_WRITER_EV_PENDING, 1u32);
}
res

View File

@ -0,0 +1,45 @@
//! Quick and dirty LiteX uart drier
const REG_RXTX: u32 = 0;
const REG_TXFULL: u32 = 0x4;
//const REG_RXEMPTY: u32 = 0x8;
use crate::{write_reg, read_reg};
use core::fmt::Write;
pub enum Error {
TxFull,
RxEmpty,
}
pub struct LiteXUart {
base_addr: u32,
}
impl LiteXUart {
pub fn new(base_addr: u32) -> Self{ Self {base_addr} }
pub fn try_put_char(&mut self, c: u8) -> Result<(), Error> {
unsafe {
if read_reg::<u32>(self.base_addr + REG_TXFULL) != 0 {
return Err(Error::TxFull);
}
write_reg::<u32>(self.base_addr + REG_RXTX, c as u32);
Ok(())
}
}
}
impl Write for LiteXUart {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
for b in s.as_bytes() {
// It's okay to loop on this because we'll always clear the buffer
while let Err(Error::TxFull) = self.try_put_char(*b) {}
//self.try_put_char(*b);
}
Ok(())
}
}

38
firmware/src/logging.rs Normal file
View File

@ -0,0 +1,38 @@
use core::{fmt::Write, any::Any};
use defmt;
use crate::uart::AmlibUart;
use core::arch::asm;
#[defmt::global_logger]
struct DefmtLogger;
unsafe impl defmt::Logger for DefmtLogger {
fn acquire() {
// Sync methods left empty because we don't use any interrupts
}
unsafe fn flush() {
// Sync methods left empty because we don't use any interrupts
}
unsafe fn release() {
// Sync methods left empty because we don't use any interrupts
}
unsafe fn write(bytes: &[u8]) {
//static mut UART: Option<AmlibUart> = None;
//if UART.is_none() {
// UART = Some(AmlibUart::new(0x0200_0040));
//}
//let mut dev = UART.unwrap();
////writeln!(dev, "a").unwrap();
////writeln!(dev, "length: {}", bytes.len());
//for byte in bytes {
// while let Err(_) = dev.try_put_char(*byte) {}
//}
}
}

View File

@ -26,10 +26,30 @@ mod eth;
mod i2c;
mod mcp4726;
mod uart;
mod litex_uart;
mod logging;
const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0];
static mut SECONDS: u32 = 0;
/// External interrupt handler
#[export_name = "MachineExternal"]
fn external_interrupt_handler() {
let cause = riscv::register::mcause::read();
let mut uart = litex_uart::LiteXUart::new(0xf000_4000);
writeln!(uart, "mcause: {}", cause.bits());
if (cause.is_interrupt()) {
let mut uart = litex_uart::LiteXUart::new(0xf000_4000);
writeln!(uart, "mcause: {}", cause.code());
if cause.code() == 1 {
// TIMER0 event, we have reset so count another second
}
}
}
// use `main` as the entry point of this application
// `main` is not allowed to return
#[entry]
@ -44,9 +64,13 @@ fn main() -> ! {
// }
//};
let blink_period = 10_000_000u32;
let mut uart = uart::AmlibUart::new(0x0200_0040);
let mut uart = litex_uart::LiteXUart::new(0xf000_4000);
writeln!(uart, "uart init");
let mut device = unsafe { eth::LiteEthDevice::try_init(0x0300_0000).unwrap() };
// enable timer
let mut device = unsafe { eth::LiteEthDevice::try_init(0xf000_0800, 0x8000_0000).unwrap() };
writeln!(uart, "eth init");
use smoltcp::wire::{EthernetAddress, HardwareAddress};
let mut config = smoltcp::iface::Config::default();
@ -74,7 +98,25 @@ fn main() -> ! {
let mut last_blink: u32 = 0;
let mut toggle = false;
defmt::info!("Done setup");
//defmt::info!("Done setup");
unsafe {
//riscv::interrupt::enable();
//riscv::register::mie::set_mext();
//riscv::register::mie::set_msoft();
// Enable UART rx event for test
//write_reg(0xf000_4014, 1u32);
// Timer stuff
write_reg(0xf000_3808, 0u32); // Disable timer
write_reg(0xf000_3800, 0u32); // Set LOAD value
write_reg(0xf000_3804, 60_000_000u32); // Set RELOAD value
write_reg(0xf000_3808, 1u32); // Enable timer
// Enable timer event
//write_reg(0xf000_381c, 1u32);
}
loop {
let now = millis();
@ -82,26 +124,47 @@ fn main() -> ! {
last_blink = now;
toggle = !toggle;
write_led(if toggle { 1 } else { 0 });
let val: u32 = unsafe {read_reg(0x8000_2000)};
writeln!(uart, "Sampler value: 0x{:08x}", val).unwrap();
}
if iface.poll(Instant::from_millis(now), &mut device, &mut socket_set) {
//writeln!(uart, "iface did something");
}
handle_timer_event();
}
}
fn busy_wait(ms: u32) {
let start = millis();
while millis() - start < ms {
unsafe {
asm!("nop");
fn handle_timer_event() {
unsafe {
if read_reg::<u32>(0xf000_3818) == 0 {
return;
}
// Clear TIMER0 event status, and update time
write_reg(0xf000_3818, 1u32);
SECONDS += 1;
}
}
fn busy_wait(ms: u32) {
//let start = millis();
//while millis() - start < ms {
// unsafe {
// asm!("nop");
// }
//}
for i in 0..ms*20_000 {
unsafe {asm!("nop");}
}
}
fn write_led(val: u32) {
unsafe {
write_reg(0x01200000, val);
write_reg(0xf000_2000, val);
}
}
@ -114,5 +177,14 @@ unsafe fn read_reg<T>(addr: u32) -> T {
}
fn millis() -> u32 {
unsafe { read_reg(0x01300000) }
riscv::interrupt::free(|| {
unsafe {
// Latch timer value
write_reg(0xf000_380c, 1u32);
// Read timer value
let val: u32 = read_reg(0xf000_3810);
let val = 60_000_000 - val;
(SECONDS * 1000) + val / 60_000
}
})
}

View File

@ -2,6 +2,7 @@ from amaranth import *
from amaranth.lib.io import pin_layout
from amaranth_soc.wishbone.bus import Interface
from amaranth_soc.memory import MemoryMap
from math import log2, ceil
__all__ = ["LiteEth", "rgmii_layout"]
@ -12,10 +13,11 @@ class LiteEth(Elaboratable, Interface):
def __init__(self, eth_interface):
self.eth_interface = eth_interface
# Addr width is 13 bits to accomodate 0x1FFF, which is well past what we care about
Interface.__init__(self, addr_width=15, data_width=32, granularity=8, features=["cti", "bte", "err"])
# TODO I need to understand the semantics here better
memory_map = MemoryMap(addr_width=17, data_width=8)
# Highest address to support is 0x0002_1FFF, so need 18 bits of full address
highest_addr = 0x0002_1FFF
bit_width = ceil(log2(highest_addr))
Interface.__init__(self, addr_width=bit_width - 2, data_width=32, granularity=8, features=["cti", "bte", "err"])
memory_map = MemoryMap(addr_width=bit_width, data_width=8)
#memory_map.add_resource(self, name="LiteETH", size=0x2000)
self.memory_map = memory_map
@ -31,7 +33,6 @@ class LiteEth(Elaboratable, Interface):
m = Module()
# TODO I have to provide TX/RX clocks myself
core = Instance(
"liteeth_core",
i_sys_clock=ClockSignal(),
@ -81,8 +82,6 @@ rgmii_layout = [
("rst", pin_layout(1, "o")),
("int_n", pin_layout(1, "i")),
# TODO is this not IO? why does LiteEth say input?
# I think the answer is it uses a primitive, not 100% right now
("mdio", pin_layout(1, "io")),
("mdc", pin_layout(1, "o")),
("rx_ctl", pin_layout(1, "i")),

199
gateware/litex_main.py Executable file
View File

@ -0,0 +1,199 @@
#!/usr/bin/env python3
#
# This file is part of LiteX-Boards.
#
# Copyright (c) 2021 Kazumoto Kojima <kkojima@rr.iij4u.or.jp>
# SPDX-License-Identifier: BSD-2-Clause
from migen import *
from litex.build.io import DDROutput
#from litex_boards.platforms import colorlight_i5
from platforms import sonar as colorlight_i5
from litex.build.lattice.trellis import trellis_args, trellis_argdict
from litex.soc.cores.clock import *
from litex.soc.integration.soc_core import *
from litex.soc.integration.builder import *
from litex.soc.cores.video import VideoHDMIPHY
from litex.soc.cores.led import LedChaser
from litex.soc.interconnect.csr import *
from litedram.modules import M12L64322A # Compatible with EM638325-6H.
from litedram.phy import GENSDRPHY, HalfRateGENSDRPHY
from liteeth.phy.ecp5rgmii import LiteEthPHYRGMII
from sampler import Sampler
from litex.soc.integration.soc import SoCRegion
# CRG ----------------------------------------------------------------------------------------------
class _CRG(Module):
def __init__(self, platform, sys_clk_freq, use_internal_osc=False, with_usb_pll=False, with_video_pll=False, sdram_rate="1:1"):
self.rst = Signal()
self.clock_domains.cd_sys = ClockDomain()
if sdram_rate == "1:2":
self.clock_domains.cd_sys2x = ClockDomain()
self.clock_domains.cd_sys2x_ps = ClockDomain()
else:
self.clock_domains.cd_sys_ps = ClockDomain()
# # #
# Clk / Rst
if not use_internal_osc:
clk = platform.request("clk25")
clk_freq = 25e6
else:
clk = Signal()
div = 5
self.specials += Instance("OSCG",
p_DIV = div,
o_OSC = clk
)
clk_freq = 310e6/div
#rst_n = platform.request("cpu_reset_n")
# PLL
self.submodules.pll = pll = ECP5PLL()
self.comb += pll.reset.eq(self.rst)
pll.register_clkin(clk, clk_freq)
pll.create_clkout(self.cd_sys, sys_clk_freq)
if sdram_rate == "1:2":
pll.create_clkout(self.cd_sys2x, 2*sys_clk_freq)
pll.create_clkout(self.cd_sys2x_ps, 2*sys_clk_freq, phase=180) # Idealy 90° but needs to be increased.
else:
pll.create_clkout(self.cd_sys_ps, sys_clk_freq, phase=180) # Idealy 90° but needs to be increased.
# SDRAM clock
sdram_clk = ClockSignal("sys2x_ps" if sdram_rate == "1:2" else "sys_ps")
self.specials += DDROutput(1, 0, platform.request("sdram_clock"), sdram_clk)
# BaseSoC ------------------------------------------------------------------------------------------
# TODO make my own platform for this based on the colorlight one, so I can export I2C and other pins
class BaseSoC(SoCCore):
def __init__(self, sys_clk_freq=60e6, eth_phy=0, with_led_chaser=True, use_internal_osc=False,
sdram_rate="1:1", with_video_terminal=False, with_video_framebuffer=False,
**kwargs):
# TODO change SRAM size
kwargs["integrated_sram_size"] = 64 * 1024
kwargs["integrated_rom_init"] = "../firmware/fw.bin"
platform = colorlight_i5.Platform(board="i9", revision="7.2", toolchain="trellis")
# CRG --------------------------------------------------------------------------------------
with_usb_pll = kwargs.get("uart_name", None) == "usb_acm"
with_video_pll = with_video_terminal or with_video_framebuffer
self.submodules.crg = _CRG(platform, sys_clk_freq,
use_internal_osc = use_internal_osc,
with_usb_pll = with_usb_pll,
with_video_pll = with_video_pll,
sdram_rate = sdram_rate
)
# SoCCore ----------------------------------------------------------------------------------
SoCCore.__init__(self, platform, int(sys_clk_freq), ident = "LiteX SoC on Sonar FPGA", **kwargs)
# Leds -------------------------------------------------------------------------------------
if with_led_chaser:
ledn = platform.request_all("user_led_n")
self.submodules.leds = LedChaser(pads=ledn, sys_clk_freq=sys_clk_freq)
# SPI Flash --------------------------------------------------------------------------------
from litespi.modules import W25Q64 as SpiFlashModule
from litespi.opcodes import SpiNorFlashOpCodes as Codes
self.add_spi_flash(mode="1x", module=SpiFlashModule(Codes.READ_1_1_1))
# SDR SDRAM --------------------------------------------------------------------------------
#if not self.integrated_main_ram_size:
# sdrphy_cls = HalfRateGENSDRPHY if sdram_rate == "1:2" else GENSDRPHY
# self.submodules.sdrphy = sdrphy_cls(platform.request("sdram"))
# self.add_sdram("sdram",
# phy = self.sdrphy,
# module = M12L64322A(sys_clk_freq, sdram_rate),
# l2_cache_size = kwargs.get("l2_size", 8192)
# )
# Ethernet / Etherbone ---------------------------------------------------------------------
self.submodules.ethphy = LiteEthPHYRGMII(
clock_pads = self.platform.request("eth_clocks", eth_phy),
pads = self.platform.request("eth", eth_phy),
tx_delay = 0)
self.add_ethernet(phy=self.ethphy)
# Video ------------------------------------------------------------------------------------
if with_video_terminal or with_video_framebuffer:
self.submodules.videophy = VideoHDMIPHY(platform.request("gpdi"), clock_domain="hdmi")
if with_video_terminal:
self.add_video_terminal(phy=self.videophy, timings="800x600@60Hz", clock_domain="hdmi")
if with_video_framebuffer:
self.add_video_framebuffer(phy=self.videophy, timings="800x600@60Hz", clock_domain="hdmi")
self.submodules.sampler = Sampler(platform.request("adc"))
sampler_region = SoCRegion(origin=None, size=0x1000, cached=False)
#self.add_wb_slave(0x9000_0000, self.sampler.bus, 0x1000)
# TODO better way to do this?
self.bus.add_slave(name="sampler", slave=self.sampler.bus, region=sampler_region)
# Build --------------------------------------------------------------------------------------------
def main():
from litex.soc.integration.soc import LiteXSoCArgumentParser
parser = LiteXSoCArgumentParser(description="LiteX SoC on Colorlight I5")
target_group = parser.add_argument_group(title="Target options")
target_group.add_argument("--build", action="store_true", help="Build design.")
target_group.add_argument("--load", action="store_true", help="Load bitstream.")
target_group.add_argument("--sys-clk-freq", default=60e6, help="System clock frequency.")
sdopts = target_group.add_mutually_exclusive_group()
sdopts.add_argument("--with-spi-sdcard", action="store_true", help="Enable SPI-mode SDCard support.")
sdopts.add_argument("--with-sdcard", action="store_true", help="Enable SDCard support.")
target_group.add_argument("--eth-phy", default=0, type=int, help="Ethernet PHY (0 or 1).")
target_group.add_argument("--use-internal-osc", action="store_true", help="Use internal oscillator.")
target_group.add_argument("--sdram-rate", default="1:1", help="SDRAM Rate (1:1 Full Rate or 1:2 Half Rate).")
viopts = target_group.add_mutually_exclusive_group()
viopts.add_argument("--with-video-terminal", action="store_true", help="Enable Video Terminal (HDMI).")
viopts.add_argument("--with-video-framebuffer", action="store_true", help="Enable Video Framebuffer (HDMI).")
builder_args(parser)
soc_core_args(parser)
trellis_args(parser)
args = parser.parse_args()
# Build firmware
import subprocess as sp
sp.run(["./build_and_strip.sh"], cwd="../firmware").check_returncode()
soc = BaseSoC(
sys_clk_freq = int(float(args.sys_clk_freq)),
eth_phy = args.eth_phy,
use_internal_osc = args.use_internal_osc,
sdram_rate = args.sdram_rate,
with_video_terminal = args.with_video_terminal,
with_video_framebuffer = args.with_video_framebuffer,
**soc_core_argdict(args)
)
soc.platform.add_extension(colorlight_i5._sdcard_pmod_io)
if args.with_spi_sdcard:
soc.add_spi_sdcard()
if args.with_sdcard:
soc.add_sdcard()
builder = Builder(soc, **builder_argdict(args))
builder_kargs = trellis_argdict(args)
if args.build:
builder.build(**builder_kargs)
if args.load:
prog = soc.platform.create_programmer()
prog.load_bitstream(builder.get_bitstream_filename(mode="sram"))
if __name__ == "__main__":
main()

233
gateware/platforms/sonar.py Normal file
View File

@ -0,0 +1,233 @@
"""
LiteX Platform for sonar board using Colorlight i9 module
"""
# This file used to belong to LiteX Boards
#
# Copyright (c) 2021 Kazumoto Kojima <kkojima@rr.iij4u.or.jp>
# Copyright (c) 2023 David Lenfesty <lenfesty@ualberta.ca>
# SPDX-License-Identifier: BSD-2-Clause
# The Colorlight i5 PCB and IOs have been documented by @wuxx
# https://github.com/wuxx/Colorlight-FPGA-Projects
import copy
from litex.build.generic_platform import *
from litex.build.lattice import LatticePlatform
from litex.build.lattice.programmer import EcpDapProgrammer
# IOs ----------------------------------------------------------------------------------------------
_io_v7_0 = [ # Documented by @smunaut
# Clk
("clk25", 0, Pins("P3"), IOStandard("LVCMOS33")),
# Led
("user_led_n", 0, Pins("U16"), IOStandard("LVCMOS33")),
# Serial
("serial", 0,
Subsignal("tx", Pins("P16")),
Subsignal("rx", Pins("L5")),
IOStandard("LVCMOS33")
),
# TODO the other serial ports
# TODO I2C
# SPIFlash (GD25Q16CSIG)
("spiflash", 0,
Subsignal("cs_n", Pins("R2")),
# https://github.com/m-labs/nmigen-boards/pull/38
#Subsignal("clk", Pins("")), driven through USRMCLK
Subsignal("mosi", Pins("W2")),
Subsignal("miso", Pins("V2")),
IOStandard("LVCMOS33"),
),
# SDRAM SDRAM (EM638325-6H)
("sdram_clock", 0, Pins("B9"), IOStandard("LVCMOS33")),
("sdram", 0,
Subsignal("a", Pins(
"B13 C14 A16 A17 B16 B15 A14 A13",
"A12 A11 B12")),
Subsignal("dq", Pins(
"D15 E14 E13 D12 E12 D11 C10 B17",
"B8 A8 C7 A7 A6 B6 A5 B5",
"D5 C5 D6 C6 E7 D7 E8 D8",
"E9 D9 E11 C11 C12 D13 D14 C15")),
Subsignal("we_n", Pins("A10")),
Subsignal("ras_n", Pins("B10")),
Subsignal("cas_n", Pins("A9")),
#Subsignal("cs_n", Pins("")), # gnd
#Subsignal("cke", Pins("")), # 3v3
Subsignal("ba", Pins("B11 C8")), # sdram pin BA0 and BA1
#Subsignal("dm", Pins("")), # gnd
IOStandard("LVCMOS33"),
Misc("SLEWRATE=FAST")
),
# RGMII Ethernet (B50612D)
# The order of the two PHYs is swapped with the naming of the connectors
# on the board so to match with the configuration of their PHYA[0] pins.
("eth_clocks", 0,
Subsignal("tx", Pins("G1")),
Subsignal("rx", Pins("H2")),
IOStandard("LVCMOS33")
),
("eth", 0,
Subsignal("rst_n", Pins("P4")),
Subsignal("mdio", Pins("P5")),
Subsignal("mdc", Pins("N5")),
Subsignal("rx_ctl", Pins("P2")),
Subsignal("rx_data", Pins("K2 L1 N1 P1")),
Subsignal("tx_ctl", Pins("K1")),
Subsignal("tx_data", Pins("G2 H1 J1 J3")),
IOStandard("LVCMOS33")
),
("eth_clocks", 1,
Subsignal("tx", Pins("U19")),
Subsignal("rx", Pins("L19")),
IOStandard("LVCMOS33")
),
("eth", 1,
Subsignal("rst_n", Pins("P4")),
Subsignal("mdio", Pins("P5")),
Subsignal("mdc", Pins("N5")),
Subsignal("rx_ctl", Pins("M20")),
Subsignal("rx_data", Pins("P20 N19 N20 M19")),
Subsignal("tx_ctl", Pins("P19")),
Subsignal("tx_data", Pins("U20 T19 T20 R20")),
IOStandard("LVCMOS33")
),
# GPDI
("gpdi", 0,
Subsignal("clk_p", Pins("J19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")),
#Subsignal("clk_n", Pins("K19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")),
Subsignal("data0_p", Pins("G19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")),
#Subsignal("data0_n", Pins("H20"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")),
Subsignal("data1_p", Pins("E20"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")),
#Subsignal("data1_n", Pins("F19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")),
Subsignal("data2_p", Pins("C20"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")),
#Subsignal("data2_n", Pins("D19"), IOStandard("LVCMOS33D"), Misc("DRIVE=4")),
),
# High speed parallel ADCs
("adc", 0,
Subsignal("data", Pins("M18 N18 N17 P18 U17 U18 T17 M17 P17 R17")),
# TODO ???? what other pins are changed in 7.2
Subsignal("refclk", Pins("L2")),
Subsignal("oen_b", Pins("K18")),
Subsignal("standby", Pins("C18")),
Subsignal("dfs", Pins("T18")),
Subsignal("otr", Pins("R18")),
IOStandard("LVCMOS33")
),
]
# From https://github.com/wuxx/Colorlight-FPGA-Projects/blob/master/schematic/i5_v6.0-extboard.pdf and
# https://github.com/wuxx/Colorlight-FPGA-Projects/blob/master/doc/i5_extboard_v1.2_pinout.png
_connectors_v7_0 = [
("pmode", "C17 B18 B20 F20 A18 A19 B19 D20"),
("pmodf", "D1 C1 C2 E3 E2 D2 B1 A3"),
]
# ColorLight i9 V 7.2 hardware
# See https://github.com/wuxx/Colorlight-FPGA-Projects/blob/master/colorlight_i9_v7.2.md
# SPIFlash (W25Q64JVSIQ)
_io_v7_2 = copy.deepcopy(_io_v7_0)
# Change the LED pin to "L2"
for i, x in enumerate(_io_v7_2):
if x[:2] == ("user_led_n", 0):
# TODO fix in HW
#_io_v7_2[i] = ("user_led_n", 0, Pins("L2"), IOStandard("LVCMOS33"))
_io_v7_2[i] = ("user_led_n", 0, Pins("J19"), IOStandard("LVCMOS33"))
break
# optional, alternative uart location
# requires "--uart-name serialx"
_io_v7_2 += [
("serialx", 0, Subsignal("tx", Pins("E5")), Subsignal("rx", Pins("F4")), IOStandard("LVCMOS33"))
]
_connectors_v7_2 = copy.deepcopy(_connectors_v7_0)
# Append the rest of the pmod interfaces
_connectors_v7_2 += [
# P2
("pmodc", "P17 R18 C18 L2 M17 R17 T18 K18"),
("pmodd", "J20 L18 M18 N17 G20 K20 L20 N18"),
# P4
("pmodg", "H4 G3 F1 F2 H3 F3 E4 E1"),
("pmodh", "- E19 B3 K5 - B2 K4 A2"),
# P5
("pmodi", "D18 G5 F5 E5 D17 D16 E6 F4"),
("pmodj", "J17 H17 H16 G16 H18 G18 F18 E18"),
# P6
("pmodk", "R3 M4 L5 J16 N4 L4 P16 J18"),
("pmodl", "R1 U1 W1 M1 T1 Y2 V1 N2"),
]
# PMODS --------------------------------------------------------------------------------------------
def sdcard_pmod_io(pmod):
return [
# SDCard PMOD:
# - https://store.digilentinc.com/pmod-microsd-microsd-card-slot/
("spisdcard", 0,
Subsignal("clk", Pins(f"{pmod}:3")),
Subsignal("mosi", Pins(f"{pmod}:1"), Misc("PULLMODE=UP")),
Subsignal("cs_n", Pins(f"{pmod}:0"), Misc("PULLMODE=UP")),
Subsignal("miso", Pins(f"{pmod}:2"), Misc("PULLMODE=UP")),
Misc("SLEWRATE=FAST"),
IOStandard("LVCMOS33"),
),
("sdcard", 0,
Subsignal("data", Pins(f"{pmod}:2 {pmod}:4 {pmod}:5 {pmod}:0"), Misc("PULLMODE=UP")),
Subsignal("cmd", Pins(f"{pmod}:1"), Misc("PULLMODE=UP")),
Subsignal("clk", Pins(f"{pmod}:3")),
Subsignal("cd", Pins(f"{pmod}:6")),
#Misc("SLEWRATE=FAST"),
IOStandard("LVCMOS33"),
),
]
_sdcard_pmod_io = sdcard_pmod_io("pmode") # SDCARD PMOD on P3.
# Platform -----------------------------------------------------------------------------------------
class Platform(LatticePlatform):
default_clk_name = "clk25"
default_clk_period = 1e9/25e6
def __init__(self, board="i5", revision="7.0", toolchain="trellis"):
if board == "i5":
assert revision in ["7.0"]
self.revision = revision
device = {"7.0": "LFE5U-25F-6BG381C"}[revision]
io = {"7.0": _io_v7_0}[revision]
connectors = {"7.0": _connectors_v7_0}[revision]
if board == "i9":
assert revision in ["7.2"]
self.revision = revision
device = {"7.2": "LFE5U-45F-6BG381C"}[revision]
io = {"7.2": _io_v7_2}[revision]
connectors = {"7.2": _connectors_v7_2}[revision]
LatticePlatform.__init__(self, device, io, connectors=connectors, toolchain=toolchain)
def create_programmer(self):
return EcpDapProgrammer()
def do_finalize(self, fragment):
LatticePlatform.do_finalize(self, fragment)
self.add_period_constraint(self.lookup_request("clk25", loose=True), 1e9/25e6)
self.add_period_constraint(self.lookup_request("eth_clocks:rx", 0, loose=True), 1e9/125e6)
self.add_period_constraint(self.lookup_request("eth_clocks:rx", 1, loose=True), 1e9/125e6)

28
gateware/sampler.py Normal file
View File

@ -0,0 +1,28 @@
from migen import *
from litex.soc.interconnect.wishbone import *
from litex.soc.integration.soc import SoCRegion
class Sampler(Module):
def __init__(self, adc_pins):
# TODO correct addr width
self.bus = Interface(data_width=32, adr_width=11)
# self.clock_domains.foo = ClockDomain() is how to add a new clock domain, accessible at self.foo
# Provide a slow clock to the ADC, 60MHz / 600 = 100kHz
self._counter = Signal(32)
self.sync += self._counter.eq(self._counter + 1)
self.sync += If(self._counter >= 600, self._counter.eq(0), adc_pins.refclk.eq(~adc_pins.refclk))
# Set config pins to constant values
self.comb += adc_pins.oen_b.eq(0) # Data pins enable
self.comb += adc_pins.standby.eq(0) # Sampling standby
self.comb += adc_pins.dfs.eq(0) # DFS (raw or two's complement)
# The only remaining pin, OTR, is an out of range status indicator
# Read directly from the data pins into the wishbone bus for now, just for bringup
self.comb += self.bus.dat_r.eq(adc_pins.data)
self.sync += self.bus.ack.eq(0)
self.sync += If(self.bus.cyc & self.bus.stb, self.bus.ack.eq(1))

37
gateware/timer.py Normal file
View File

@ -0,0 +1,37 @@
from amaranth import *
from amaranth_soc.wishbone import *
from amaranth_soc.memory import *
from math import ceil, log2
class TimerPeripheral(Elaboratable, Interface):
def __init__(self, clock_freq: int, wanted_freq: int):
Interface.__init__(self, addr_width=1, data_width=32, granularity=8)
memory_map = MemoryMap(addr_width=3, data_width=8)
self.memory_map = memory_map
self.ratio = ceil(clock_freq / wanted_freq)
def elaborate(self, platform):
m = Module()
counter = Signal(ceil(log2(self.ratio)))
value = Signal(32)
# Up count
m.d.sync += counter.eq(counter + 1)
# Divider value reached, increment
with m.If(counter >= self.ratio):
m.d.sync += [
value.eq(value + 1),
counter.eq(0),
]
m.d.sync += self.ack.eq(0)
with m.If(self.cyc & self.stb):
m.d.sync += [
self.ack.eq(1),
self.dat_r.eq(value),
]
return m

View File

@ -15,3 +15,6 @@ Designed for JLC7628 stackup.
- I2C should have DNP pullup resistor footprints
- Reset and/or power button would be nice
- Pads on DDR connector could be thinned slightly
- VREF is floating on ADCs
- led is on same FPGA pin as ADC1 refclk (U16)
- Need to figure out the pin length for mounting the board directly to preprocessor