Compare commits
6 Commits
f846cc1fab
...
ac746a8c5a
Author | SHA1 | Date | |
---|---|---|---|
ac746a8c5a | |||
bf482e740d | |||
a05af8739c | |||
348f6d5ba6 | |||
96dabe013a | |||
32fb8383d1 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
|||||||
[submodule "gateware/amaranth-boards"]
|
|
||||||
path = gateware/amaranth-boards
|
|
||||||
url = https://github.com/amaranth-lang/amaranth-boards
|
|
||||||
[submodule "gateware/jtagtap"]
|
[submodule "gateware/jtagtap"]
|
||||||
path = gateware/jtagtap
|
path = gateware/jtagtap
|
||||||
url = git@github.com:davidlenfesty/jtagtap
|
url = git@github.com:davidlenfesty/jtagtap
|
||||||
|
@ -9,3 +9,6 @@ edition = "2021"
|
|||||||
riscv-rt = "0.11.0"
|
riscv-rt = "0.11.0"
|
||||||
panic-halt = "0.2.0"
|
panic-halt = "0.2.0"
|
||||||
embedded-hal = "0.2.7"
|
embedded-hal = "0.2.7"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
RAM : ORIGIN = 0x01001000, LENGTH = 4K
|
RAM : ORIGIN = 0x01002000, LENGTH = 4K
|
||||||
FLASH : ORIGIN = 0x01000000, LENGTH = 4K
|
FLASH : ORIGIN = 0x01000000, LENGTH = 8K
|
||||||
}
|
}
|
||||||
|
|
||||||
REGION_ALIAS("REGION_TEXT", FLASH);
|
REGION_ALIAS("REGION_TEXT", FLASH);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
//! Quick and hacky ethernet thing to test
|
//! Quick and hacky ethernet thing to test
|
||||||
|
|
||||||
const LITEETH_BASE: u32 = 0x0200_0000;
|
const LITEETH_BASE: u32 = 0x0300_0000;
|
||||||
|
|
||||||
const ETHMAC_SRAM_WRITER_EV_PENDING: u32 = LITEETH_BASE + 0x810;
|
const ETHMAC_SRAM_WRITER_EV_PENDING: u32 = LITEETH_BASE + 0x810;
|
||||||
const ETHMAC_SRAM_WRITER_EV_ENABLE: u32 = LITEETH_BASE + 0x814;
|
const ETHMAC_SRAM_WRITER_EV_ENABLE: u32 = LITEETH_BASE + 0x814;
|
||||||
|
104
firmware/src/i2c.rs
Normal file
104
firmware/src/i2c.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
//! I2C driver for my amlib i2c interface implementation.
|
||||||
|
//!
|
||||||
|
//! See `gateware/i2c.py` for register information
|
||||||
|
|
||||||
|
|
||||||
|
/// TODO repr(C) a register bank, and add instances
|
||||||
|
|
||||||
|
// Using the blocking API because the peripheral requires fairly tight timing to operate
|
||||||
|
// correctly, and I don't feel like writing the gateware to resolve that.
|
||||||
|
use embedded_hal::blocking::i2c::{Write, Read, SevenBitAddress, Transactional, Operation};
|
||||||
|
use crate::{read_reg, write_reg};
|
||||||
|
use core::arch::asm;
|
||||||
|
|
||||||
|
// TODO I think there may be bus address semantics I'm not 100% on.
|
||||||
|
// There's a possiblity these addresses are wrong, and they need to be 4 bytes each
|
||||||
|
const CR: u32 = 0;
|
||||||
|
const SR: u32 = 1;
|
||||||
|
const DWR: u32 = 2;
|
||||||
|
const DRR: u32 = 3;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
/// Device is busy for some reason
|
||||||
|
Busy,
|
||||||
|
/// I2C bus returned a NACK
|
||||||
|
Nack,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AmlibI2c {
|
||||||
|
base_addr: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AmlibI2c {
|
||||||
|
pub fn new(base_addr: u32) -> Self {
|
||||||
|
AmlibI2c { base_addr }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_while_busy(&self) {
|
||||||
|
unsafe {
|
||||||
|
while read_reg::<u32>(self.base_addr + SR) & 1 != 0 {
|
||||||
|
asm!("nop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write<SevenBitAddress> for AmlibI2c {
|
||||||
|
// TODO errors
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
unsafe {
|
||||||
|
if (read_reg::<u32>(self.base_addr + SR) & 1) != 0 {
|
||||||
|
return Err(Error::Busy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// START
|
||||||
|
write_reg(self.base_addr + CR, 0x01u32);
|
||||||
|
// Pre-load data w/ address (R/~W = 0)
|
||||||
|
write_reg(self.base_addr + DWR, (address << 1) as u32);
|
||||||
|
|
||||||
|
self.wait_while_busy();
|
||||||
|
|
||||||
|
// Send address byte
|
||||||
|
write_reg(self.base_addr + CR, 0x04u32);
|
||||||
|
if read_reg::<u32>(self.base_addr + SR) & 0x02 != 0 {
|
||||||
|
return Err(Error::Nack);
|
||||||
|
}
|
||||||
|
|
||||||
|
for byte in bytes {
|
||||||
|
// Write byte
|
||||||
|
write_reg(self.base_addr + DWR, *byte as u32);
|
||||||
|
self.wait_while_busy();
|
||||||
|
// Send byte once done sending the last byte
|
||||||
|
write_reg(self.base_addr + CR, 0x04u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wait_while_busy();
|
||||||
|
// STOP
|
||||||
|
write_reg(self.base_addr + CR, 0x02u32);
|
||||||
|
|
||||||
|
self.wait_while_busy();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read<SevenBitAddress> for AmlibI2c {
|
||||||
|
// TODO errors
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transactional for AmlibI2c {
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn exec<'a>(&mut self, address: u8, operations: &mut [Operation<'a>])
|
||||||
|
-> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -4,12 +4,14 @@
|
|||||||
extern crate panic_halt;
|
extern crate panic_halt;
|
||||||
|
|
||||||
use core::{arch::asm, ptr::{write, read}};
|
use core::{arch::asm, ptr::{write, read}};
|
||||||
|
use core::fmt::Write;
|
||||||
|
|
||||||
use embedded_hal::prelude::_embedded_hal_blocking_i2c_Write;
|
use embedded_hal::prelude::_embedded_hal_blocking_i2c_Write;
|
||||||
use riscv_rt::entry;
|
use riscv_rt::entry;
|
||||||
|
|
||||||
mod eth;
|
mod eth;
|
||||||
mod i2c;
|
mod i2c;
|
||||||
|
mod uart;
|
||||||
|
|
||||||
// use `main` as the entry point of this application
|
// use `main` as the entry point of this application
|
||||||
// `main` is not allowed to return
|
// `main` is not allowed to return
|
||||||
@ -26,12 +28,15 @@ fn main() -> ! {
|
|||||||
//};
|
//};
|
||||||
let blink_period = 10_000_000u32;
|
let blink_period = 10_000_000u32;
|
||||||
|
|
||||||
let mut i2c = i2c::AmlibI2c::new(0x01003000);
|
//let mut i2c = i2c::AmlibI2c::new(0x0200_0000);
|
||||||
let data = [0u8, 2u8];
|
//let data = [0u8, 2u8];
|
||||||
i2c.write(0xAA, &data).unwrap();
|
//i2c.write(0xAA, &data).unwrap();
|
||||||
|
|
||||||
|
let mut uart = uart::AmlibUart::new(0x0200_0040);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
//eth::tranmsit();
|
//eth::tranmsit();
|
||||||
|
uart.write_str("Hello world!\r\n");
|
||||||
write_led(0);
|
write_led(0);
|
||||||
busy_wait(blink_period);
|
busy_wait(blink_period);
|
||||||
write_led(1);
|
write_led(1);
|
||||||
@ -48,7 +53,7 @@ fn busy_wait(num_nops: u32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_led(val: u32) {
|
fn write_led(val: u32) {
|
||||||
unsafe { write_reg(0x0100200, val); }
|
unsafe { write_reg(0x01003000, val); }
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn write_reg<T>(addr: u32, value: T) {
|
unsafe fn write_reg<T>(addr: u32, value: T) {
|
||||||
|
75
firmware/src/uart.rs
Normal file
75
firmware/src/uart.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
//! Quick and dirty uart driver
|
||||||
|
|
||||||
|
/// TODO repr(C) a register bank, and add instances
|
||||||
|
///
|
||||||
|
use core::ptr::{read_volatile, write_volatile};
|
||||||
|
use core::fmt::{Write};
|
||||||
|
|
||||||
|
// TODO these offsets may be wrong. I'm still unsure about CSR semantics
|
||||||
|
const REG_DIVISOR_OFFSET: u32 = 0;
|
||||||
|
const REG_SR_OFFSET: u32 = 2;
|
||||||
|
const REG_DR_OFFSET: u32 = 3;
|
||||||
|
|
||||||
|
pub const FLAG_SR_TX_FULL: u8 = (1 << 0);
|
||||||
|
pub const FLAG_SR_TX_EMPTY: u8 = (1 << 1);
|
||||||
|
pub const FLAG_SR_RX_FULL: u8 = (1 << 2);
|
||||||
|
pub const FLAG_SR_RX_EMPTY: u8 = (1 << 3);
|
||||||
|
|
||||||
|
pub enum Error {
|
||||||
|
TxFull,
|
||||||
|
RxEmpty,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AmlibUart {
|
||||||
|
base_addr: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AmlibUart {
|
||||||
|
pub fn new(base_addr: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
base_addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn read_status(&mut self) -> u8 {
|
||||||
|
unsafe {
|
||||||
|
read_volatile((self.base_addr + REG_SR_OFFSET) as *const u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_get_char(&mut self) -> Result<u8, Error> {
|
||||||
|
if self.read_status() & FLAG_SR_RX_EMPTY != 0 {
|
||||||
|
return Err(Error::RxEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
Ok(read_volatile((self.base_addr + REG_DR_OFFSET) as *const u8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_put_char(&mut self, c: u8) -> Result<(), Error> {
|
||||||
|
if self.read_status() & FLAG_SR_TX_FULL != 0 {
|
||||||
|
return Err(Error::TxFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
write_volatile((self.base_addr + REG_DR_OFFSET) as *mut u8, c);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocking implementation of write
|
||||||
|
impl Write for AmlibUart {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
Subproject commit 1d82f2ece15ddcce964b9d3be1d13e8a343537eb
|
|
@ -41,7 +41,7 @@ class I2C(Elaboratable):
|
|||||||
# [3]: read - read a byte from the bus
|
# [3]: read - read a byte from the bus
|
||||||
# [4]: read_ack - ACK value that gets written out during a read operation
|
# [4]: read_ack - ACK value that gets written out during a read operation
|
||||||
# [5]: read_ack_en - Hacky solution to determine if we want to save read_ack
|
# [5]: read_ack_en - Hacky solution to determine if we want to save read_ack
|
||||||
self.CR = Element(6, Element.Access.W, name="CR")
|
self.CR = Element(6, Element.Access.W, name="I2C_CR")
|
||||||
|
|
||||||
# Status register
|
# Status register
|
||||||
#
|
#
|
||||||
@ -49,27 +49,28 @@ class I2C(Elaboratable):
|
|||||||
# [0]: busy - bus is busy operating
|
# [0]: busy - bus is busy operating
|
||||||
# [1]: ack - an ACK has been received from a bus slave
|
# [1]: ack - an ACK has been received from a bus slave
|
||||||
# [2]: read_ack - a convenience read field to see value of CR->read_ack
|
# [2]: read_ack - a convenience read field to see value of CR->read_ack
|
||||||
self.SR = Element(3, Element.Access.R, name="SR")
|
self.SR = Element(3, Element.Access.R, name="I2C_SR")
|
||||||
|
|
||||||
# Data write register
|
# Data write register
|
||||||
#
|
#
|
||||||
# Latches in data to be written when write signal is applied.
|
# Latches in data to be written when write signal is applied.
|
||||||
self.DWR = Element(8, Element.Access.W, name="DWR")
|
self.DWR = Element(8, Element.Access.W, name="I2C_DWR")
|
||||||
|
|
||||||
# Data read register
|
# Data read register
|
||||||
#
|
#
|
||||||
# Only presents valid data after 'read' has started, and once 'busy' is no longer asserted.
|
# Only presents valid data after 'read' has started, and once 'busy' is no longer asserted.
|
||||||
self.DRR = Element(8, Element.Access.R, name="DRR")
|
self.DRR = Element(8, Element.Access.R, name="I2C_DRR")
|
||||||
|
|
||||||
# Set up CSR bus
|
# Set up CSR bus
|
||||||
addr_width = ceil(log2(64)) # Support up to 64 registers just because
|
addr_width = ceil(log2(64)) # Support up to 64 registers just because
|
||||||
data_width = 8 # 32 bit bus
|
data_width = 8 # 32 bit bus
|
||||||
self._csr_mux = Multiplexer(addr_width=addr_width, data_width=data_width)
|
self._csr_mux = Multiplexer(addr_width=addr_width, data_width=data_width)
|
||||||
# TODO export the addresses of these somehow
|
# TODO export these addresses into some config file
|
||||||
self._csr_mux.add(self.CR)
|
cr_start, _stop = self._csr_mux.add(self.CR)
|
||||||
self._csr_mux.add(self.SR)
|
sr_start, _stop = self._csr_mux.add(self.SR)
|
||||||
self._csr_mux.add(self.DWR)
|
dwr_start, _stop = self._csr_mux.add(self.DWR)
|
||||||
self._csr_mux.add(self.DRR)
|
drr_start, _stop = self._csr_mux.add(self.DRR)
|
||||||
|
print(f"I2C added. CR 0x{cr_start:x}, SR 0x{sr_start:x}, DWR 0x{dwr_start:x}, DRR 0x{drr_start:x}")
|
||||||
self.bus = self._csr_mux.bus
|
self.bus = self._csr_mux.bus
|
||||||
|
|
||||||
# Set up I2C initiator submodule
|
# Set up I2C initiator submodule
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from amaranth import *
|
from amaranth import *
|
||||||
from amaranth.sim import *
|
from amaranth.sim import *
|
||||||
from amaranth_boards import colorlight_i9
|
from platforms import *
|
||||||
from amaranth_soc.wishbone import *
|
from amaranth_soc.wishbone import *
|
||||||
from amaranth_soc import csr
|
from amaranth_soc import csr
|
||||||
from amaranth_soc.csr.wishbone import WishboneCSRBridge
|
from amaranth_soc.csr.wishbone import WishboneCSRBridge
|
||||||
@ -122,7 +122,7 @@ class Core(Elaboratable):
|
|||||||
fw = load_firmware_for_mem()
|
fw = load_firmware_for_mem()
|
||||||
|
|
||||||
# Hook up memory space
|
# Hook up memory space
|
||||||
self.rom = ROM(fw)
|
self.rom = ROM(size_bytes=8192, data=fw)
|
||||||
m.submodules.rom = self.rom
|
m.submodules.rom = self.rom
|
||||||
# Problem: not sure to handle how we do byte vs word addressing properly
|
# Problem: not sure to handle how we do byte vs word addressing properly
|
||||||
# So doing this shift is a bit of a hacky way to impl anything
|
# So doing this shift is a bit of a hacky way to impl anything
|
||||||
@ -143,10 +143,8 @@ class Core(Elaboratable):
|
|||||||
# Create CSR bus and connect it to Wishbone
|
# Create CSR bus and connect it to Wishbone
|
||||||
self.csr = csr.Decoder(addr_width=10, data_width=8)
|
self.csr = csr.Decoder(addr_width=10, data_width=8)
|
||||||
m.submodules.csr = self.csr
|
m.submodules.csr = self.csr
|
||||||
print(f"CSR bus added at 0x{start:08x}")
|
|
||||||
|
|
||||||
# I2C (connected to DAC for VCO and ADC?)
|
# I2C (connected to DAC for VCO and ADC?)
|
||||||
Signal()
|
|
||||||
if platform is not None:
|
if platform is not None:
|
||||||
i2c_pads = platform.request("i2c")
|
i2c_pads = platform.request("i2c")
|
||||||
else:
|
else:
|
||||||
@ -158,17 +156,29 @@ class Core(Elaboratable):
|
|||||||
m.d.comb += i2c_pads.scl.i.eq(1)
|
m.d.comb += i2c_pads.scl.i.eq(1)
|
||||||
self.i2c = i2c.I2C(50e6, 100e3, i2c_pads)
|
self.i2c = i2c.I2C(50e6, 100e3, i2c_pads)
|
||||||
m.submodules.i2c = self.i2c
|
m.submodules.i2c = self.i2c
|
||||||
self.csr.add(self.i2c.bus)
|
i2c_start, _stop, _step = self.csr.add(self.i2c.bus)
|
||||||
|
print(f"LED added to CSR at 0x{i2c_start}")
|
||||||
|
|
||||||
|
if platform is not None:
|
||||||
|
uart_pads = platform.request("uart", 1)
|
||||||
|
else:
|
||||||
|
uart_pads = None
|
||||||
|
# TODO spread sysclk freq through design
|
||||||
|
self.uart = uart.UART(50e6, 115_200, pins=uart_pads)
|
||||||
|
m.submodules.uart = self.uart
|
||||||
|
uart_start, _stop, _step = self.csr.add(self.uart.bus)
|
||||||
|
print(f"UART added to CSR at 0x{uart_start:x}")
|
||||||
|
|
||||||
self.csr_bridge = WishboneCSRBridge(self.csr.bus, data_width=32, name="CSR")
|
self.csr_bridge = WishboneCSRBridge(self.csr.bus, data_width=32, name="CSR")
|
||||||
m.submodules.csr_bridge = self.csr_bridge
|
m.submodules.csr_bridge = self.csr_bridge
|
||||||
# TODO shouldn't have to hard-specify this address
|
# TODO shouldn't have to hard-specify this address
|
||||||
start, _stop, _step = self.decoder.add(self.csr_bridge.wb_bus, addr=0x01003000)
|
start, _stop, _step = self.decoder.add(self.csr_bridge.wb_bus, addr=0x02000000)
|
||||||
|
print(f"CSR bus added at 0x{start:08x}")
|
||||||
|
|
||||||
# Ethernet
|
# Ethernet
|
||||||
self.eth = LiteEth(self.eth_interface)
|
self.eth = LiteEth(self.eth_interface)
|
||||||
m.submodules.eth = self.eth
|
m.submodules.eth = self.eth
|
||||||
#start, _stop, _step = self.decoder.add(self.eth, addr=0x02000000)
|
start, _stop, _step = self.decoder.add(self.eth, addr=0x03000000)
|
||||||
print(f"LiteETH added at 0x{start:08x}")
|
print(f"LiteETH added at 0x{start:08x}")
|
||||||
|
|
||||||
# Connect arbiter to decoder
|
# Connect arbiter to decoder
|
||||||
@ -187,6 +197,7 @@ class SoC(Elaboratable):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def elaborate(self, platform):
|
def elaborate(self, platform):
|
||||||
|
# TODO pull I2C and UART into here instead of the "core"
|
||||||
if platform is not None:
|
if platform is not None:
|
||||||
clk25 = platform.request("clk25")
|
clk25 = platform.request("clk25")
|
||||||
led_signal = platform.request("led")
|
led_signal = platform.request("led")
|
||||||
@ -229,7 +240,7 @@ if __name__ == "__main__":
|
|||||||
if args.build:
|
if args.build:
|
||||||
# Overrides are available via AMARANTH_<override_variable_name> env variable, or kwarg
|
# Overrides are available via AMARANTH_<override_variable_name> env variable, or kwarg
|
||||||
# TODO fix platform so I don't have to manually specify MDIO signal
|
# TODO fix platform so I don't have to manually specify MDIO signal
|
||||||
colorlight_i9.Colorlight_i9_Platform().build(SoC(), debug_verilog=args.gen_debug_verilog, nextpnr_opts="--router router1", add_preferences="LOCATE COMP \"top.eth.core.rgmii_eth_mdio\" SITE \"P5\";\n")
|
Colorlight_i9_Platform().build(SoC(), debug_verilog=args.gen_debug_verilog, nextpnr_opts="--router router1", add_preferences="LOCATE COMP \"top.eth.core.rgmii_eth_mdio\" SITE \"P5\";\n")
|
||||||
|
|
||||||
if args.test:
|
if args.test:
|
||||||
if args.save_vcd:
|
if args.save_vcd:
|
||||||
|
@ -2,25 +2,28 @@ from amaranth import *
|
|||||||
from amaranth_soc.wishbone import *
|
from amaranth_soc.wishbone import *
|
||||||
from amaranth_soc.memory import *
|
from amaranth_soc.memory import *
|
||||||
|
|
||||||
|
from math import log2, ceil
|
||||||
|
|
||||||
# TODO impl select
|
# TODO impl select
|
||||||
|
|
||||||
|
|
||||||
# We sub-class wishbone.Interface here because it needs to be a bus object to be added as a window to Wishbone stuff
|
# We sub-class wishbone.Interface here because it needs to be a bus object to be added as a window to Wishbone stuff
|
||||||
class ROM(Elaboratable, Interface):
|
class ROM(Elaboratable, Interface):
|
||||||
def __init__(self, data=None):
|
def __init__(self, *, size_bytes=4096, data=None):
|
||||||
#self.size = len(data)
|
#self.size = len(data)
|
||||||
self.data = Memory(width=32, depth=(4096 >> 2), init=data)
|
addr_width = ceil(log2(size_bytes >> 2))
|
||||||
|
self.data = Memory(width=32, depth=size_bytes >> 2, init=data)
|
||||||
self.r = self.data.read_port()
|
self.r = self.data.read_port()
|
||||||
|
|
||||||
# Need to init Interface
|
# Need to init Interface
|
||||||
Interface.__init__(self, addr_width=10, data_width=32, granularity=8)
|
Interface.__init__(self, addr_width=addr_width, data_width=32, granularity=8)
|
||||||
|
|
||||||
# This is effectively a "window", and it has a certain set of resources
|
# This is effectively a "window", and it has a certain set of resources
|
||||||
# 12 = log2(4096)
|
# 12 = log2(4096)
|
||||||
memory_map = MemoryMap(addr_width=12, data_width=8)
|
memory_map = MemoryMap(addr_width=addr_width + 2, data_width=8)
|
||||||
# TODO need to unify how I deal with size
|
# TODO need to unify how I deal with size
|
||||||
# In this case, one resource, which is out memory
|
# In this case, one resource, which is out memory
|
||||||
memory_map.add_resource(self.data, name="rom_data", size=(4096 >> 2))
|
memory_map.add_resource(self.data, name="rom_data", size=size_bytes)
|
||||||
|
|
||||||
self.memory_map = memory_map
|
self.memory_map = memory_map
|
||||||
|
|
||||||
@ -52,21 +55,21 @@ class ROM(Elaboratable, Interface):
|
|||||||
# TODO support read segmentation or whatever it's called, where you read/write certian bytes from memory
|
# TODO support read segmentation or whatever it's called, where you read/write certian bytes from memory
|
||||||
# Otherwise we can't store individual bytes, and this will wreck shit in weird ways.
|
# Otherwise we can't store individual bytes, and this will wreck shit in weird ways.
|
||||||
class RAM(Elaboratable, Interface):
|
class RAM(Elaboratable, Interface):
|
||||||
def __init__(self):
|
def __init__(self, *, size_bytes=4096):
|
||||||
#self.size = len(data)
|
addr_width = ceil(log2(size_bytes >> 2))
|
||||||
self.data = Memory(width=32, depth=(4096 >> 2))
|
self.data = Memory(width=32, depth=size_bytes >> 2)
|
||||||
self.r = self.data.read_port()
|
self.r = self.data.read_port()
|
||||||
self.w = self.data.write_port()
|
self.w = self.data.write_port()
|
||||||
|
|
||||||
# Need to init Interface
|
# Need to init Interface
|
||||||
Interface.__init__(self, addr_width=10, data_width=32, granularity=8)
|
Interface.__init__(self, addr_width=addr_width, data_width=32, granularity=8)
|
||||||
|
|
||||||
# This is effectively a "window", and it has a certain set of resources
|
# This is effectively a "window", and it has a certain set of resources
|
||||||
# 12 = log2(4096)
|
# 12 = log2(4096)
|
||||||
memory_map = MemoryMap(addr_width=12, data_width=8)
|
memory_map = MemoryMap(addr_width=addr_width + 2, data_width=8)
|
||||||
# TODO need to unify how I deal with size
|
# TODO need to unify how I deal with size
|
||||||
# In this case, one resource, which is out memory
|
# In this case, one resource, which is out memory
|
||||||
memory_map.add_resource(self.data, name="ram_data", size=(4096 >> 2))
|
memory_map.add_resource(self.data, name="ram_data", size=size_bytes)
|
||||||
|
|
||||||
self.memory_map = memory_map
|
self.memory_map = memory_map
|
||||||
|
|
||||||
|
1
gateware/platforms/__init__.py
Normal file
1
gateware/platforms/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .colorlight_i9 import *
|
136
gateware/platforms/colorlight_i9.py
Normal file
136
gateware/platforms/colorlight_i9.py
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
from amaranth.build import *
|
||||||
|
from amaranth.vendor.lattice_ecp5 import *
|
||||||
|
from amaranth_boards.resources import *
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["Colorlight_i9_Platform"]
|
||||||
|
|
||||||
|
|
||||||
|
class Colorlight_i9_Platform(LatticeECP5Platform):
|
||||||
|
device = "LFE5U-45F"
|
||||||
|
package = "BG381"
|
||||||
|
speed = "6"
|
||||||
|
default_clk = "clk25"
|
||||||
|
|
||||||
|
resources = [
|
||||||
|
Resource("clk25", 0, Pins("P3", dir="i"), Clock(25e6), Attrs(IO_TYPE="LVCMOS33")),
|
||||||
|
|
||||||
|
*LEDResources(pins="L2", invert = True,
|
||||||
|
attrs=Attrs(IO_TYPE="LVCMOS33", DRIVE="4")),
|
||||||
|
|
||||||
|
#*ButtonResources(pins="M13", invert = True,
|
||||||
|
# attrs=Attrs(IO_TYPE="LVCMOS33", PULLMODE="UP")),
|
||||||
|
|
||||||
|
UARTResource(0,
|
||||||
|
tx="E17",
|
||||||
|
rx="D18",
|
||||||
|
attrs=Attrs(IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
UARTResource(1,
|
||||||
|
tx="P16",
|
||||||
|
rx="L5",
|
||||||
|
attrs=Attrs(IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
UARTResource(2,
|
||||||
|
tx="J18",
|
||||||
|
rx="J16",
|
||||||
|
attrs=Attrs(IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
|
||||||
|
# SPIFlash (W25Q32JV) 1x/2x/4x speed
|
||||||
|
Resource("spi_flash", 0,
|
||||||
|
Subsignal("cs", PinsN("R2", dir="o")),
|
||||||
|
# Subsignal("clk", Pins("", dir="i")), # driven through USRMCLK
|
||||||
|
Subsignal("cipo", Pins("V2", dir="i")), # Chip: DI/IO0
|
||||||
|
Subsignal("copi", Pins("W2", dir="o")), # DO/IO1
|
||||||
|
Attrs(IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
|
||||||
|
# 2x ESMT M12L16161A-5T 1M x 16bit 200MHz SDRAMs (organized as 1M x 32bit)
|
||||||
|
# 2x WinBond W9816G6JH-6 1M x 16bit 166MHz SDRAMs (organized as 1M x 32bit) are lso reported
|
||||||
|
SDRAMResource(0,
|
||||||
|
clk="B9", we_n="A10", cas_n="A9", ras_n="B10",
|
||||||
|
ba="B11 C8", a="B13 C14 A16 A17 B16 B15 A14 A13 A12 A11 B12",
|
||||||
|
dq="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",
|
||||||
|
attrs=Attrs(PULLMODE="NONE", DRIVE="4", SLEWRATE="FAST", IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
|
||||||
|
# Broadcom B50612D Gigabit Ethernet Transceiver
|
||||||
|
Resource("eth_rgmii", 0,
|
||||||
|
Subsignal("rst", PinsN("P4", dir="o")),
|
||||||
|
Subsignal("mdc", Pins("N5", dir="o")),
|
||||||
|
#Subsignal("mdio", Pins("P5", dir="io")),
|
||||||
|
Subsignal("tx_clk", Pins("U19", dir="o")),
|
||||||
|
Subsignal("tx_ctl", Pins("P19", dir="o")),
|
||||||
|
Subsignal("tx_data", Pins("U20 T19 T20 R20", dir="o")),
|
||||||
|
Subsignal("rx_clk", Pins("L19", dir="i")),
|
||||||
|
Subsignal("rx_ctl", Pins("M20", dir="i")),
|
||||||
|
Subsignal("rx_data", Pins("P20 N19 N20 M19", dir="i")),
|
||||||
|
Attrs(IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
|
||||||
|
# Broadcom B50612D Gigabit Ethernet Transceiver
|
||||||
|
Resource("eth_rgmii", 1,
|
||||||
|
Subsignal("rst", PinsN("P4", dir="o")),
|
||||||
|
Subsignal("mdc", Pins("N5", dir="o")),
|
||||||
|
#Subsignal("mdio", Pins("P5", dir="io")),
|
||||||
|
Subsignal("tx_clk", Pins("G1", dir="o")),
|
||||||
|
Subsignal("tx_ctl", Pins("K1", dir="o")),
|
||||||
|
Subsignal("tx_data", Pins("G2 H1 J1 J3", dir="o")),
|
||||||
|
Subsignal("rx_clk", Pins("H2", dir="i")),
|
||||||
|
Subsignal("rx_ctl", Pins("P2", dir="i")),
|
||||||
|
Subsignal("rx_data", Pins("K2 L1 N1 P1", dir="i")),
|
||||||
|
Attrs(IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
|
||||||
|
Resource("jtag", 0,
|
||||||
|
Subsignal("trst", Pins("J17", dir="i")),
|
||||||
|
Subsignal("tck", Pins("G18", dir="i")),
|
||||||
|
Subsignal("tms", Pins("H16", dir="i")),
|
||||||
|
Subsignal("tdo", Pins("H17", dir="o")),
|
||||||
|
Subsignal("tdi", Pins("H18", dir="i")),
|
||||||
|
Attrs(IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
|
||||||
|
Resource("i2c", 0,
|
||||||
|
Subsignal("sda", Pins("D16", dir="io")),
|
||||||
|
Subsignal("scl", Pins("F5", dir="io")), # Hacky stuff for now, amlib needs it to be io for some reason
|
||||||
|
Attrs(IO_TYPE="LVCMOS33")
|
||||||
|
),
|
||||||
|
]
|
||||||
|
connectors = []
|
||||||
|
# Connector("j", 1, "F3 F1 G3 - G2 H3 H5 F15 L2 K1 J5 K2 B16 J14 F12 -"),
|
||||||
|
# Connector("j", 2, "J4 K3 G1 - K4 C2 E3 F15 L2 K1 J5 K2 B16 J14 F12 -"),
|
||||||
|
# Connector("j", 3, "H4 K5 P1 - R1 L5 F2 F15 L2 K1 J5 K2 B16 J14 F12 -"),
|
||||||
|
# Connector("j", 4, "P4 R2 M8 - M9 T6 R6 F15 L2 K1 J5 K2 B16 J14 F12 -"),
|
||||||
|
# Connector("j", 5, "M11 N11 P12 - K15 N12 L16 F15 L2 K1 J5 K2 B16 J14 F12 -"),
|
||||||
|
# Connector("j", 6, "K16 J15 J16 - J12 H15 G16 F15 L2 K1 J5 K2 B16 J14 F12 -"),
|
||||||
|
# Connector("j", 7, "H13 J13 H12 - G14 H14 G15 F15 L2 K1 J5 K2 B16 J14 F12 -"),
|
||||||
|
# Connector("j", 8, "A15 F16 A14 - E13 B14 A13 F15 L2 K1 J5 K2 B16 J14 F12 -"),
|
||||||
|
# Connector("j", 19, " - M13 - - P11"),
|
||||||
|
#]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def required_tools(self):
|
||||||
|
return super().required_tools + [
|
||||||
|
"ecpdap"
|
||||||
|
]
|
||||||
|
|
||||||
|
def toolchain_prepare(self, fragment, name, **kwargs):
|
||||||
|
overrides = dict(ecppack_opts="--compress")
|
||||||
|
overrides.update(kwargs)
|
||||||
|
return super().toolchain_prepare(fragment, name, **overrides)
|
||||||
|
|
||||||
|
def toolchain_program(self, products, name):
|
||||||
|
tool = os.environ.get("ECPDAP", "ecpdap")
|
||||||
|
with products.extract("{}.bit".format(name)) as bitstream_filename:
|
||||||
|
subprocess.check_call([tool, "program", bitstream_filename, "--freq", "10M"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
from .test.blinky import *
|
||||||
|
Colorlight_i9_Platform().build(Blinky(), do_program=True)
|
@ -38,7 +38,7 @@ class UART(Elaboratable):
|
|||||||
#
|
#
|
||||||
# Sets input/output baudrate to system clock / divisor. Resets to value
|
# Sets input/output baudrate to system clock / divisor. Resets to value
|
||||||
# that provides 115200 baud rate. Writes to this register clear FIFOs.
|
# that provides 115200 baud rate. Writes to this register clear FIFOs.
|
||||||
self.DIVISOR = Element(16, Element.Access.RW, name="DIVISOR")
|
self.DIVISOR = Element(16, Element.Access.RW, name="UART_DIVISOR")
|
||||||
|
|
||||||
# Status register.
|
# Status register.
|
||||||
#
|
#
|
||||||
@ -47,22 +47,23 @@ class UART(Elaboratable):
|
|||||||
# [1]: txfifo_empty
|
# [1]: txfifo_empty
|
||||||
# [2]: rxfifo_full
|
# [2]: rxfifo_full
|
||||||
# [3]: rxfifo_empty
|
# [3]: rxfifo_empty
|
||||||
self.SR = Element(4, Element.Access.R, name="SR")
|
self.SR = Element(4, Element.Access.R, name="UART_SR")
|
||||||
|
|
||||||
# Data register.
|
# Data register.
|
||||||
#
|
#
|
||||||
# Writes push data into TX FIFO, and are discarded if full, reads pull
|
# Writes push data into TX FIFO, and are discarded if full, reads pull
|
||||||
# data from RX FIFO, and are invalid if it is empty. Incoming bytes are discarded
|
# data from RX FIFO, and are invalid if it is empty. Incoming bytes are discarded
|
||||||
# if the RX FIFO is full.
|
# if the RX FIFO is full.
|
||||||
self.DR = Element(8, Element.Access.RW, name="DR")
|
self.DR = Element(8, Element.Access.RW, name="UART_DR")
|
||||||
|
|
||||||
# Set up CSR bus
|
# Set up CSR bus
|
||||||
addr_width = ceil(log2(64))
|
addr_width = ceil(log2(64))
|
||||||
data_width = 32
|
data_width = 8
|
||||||
self._csr_mux = Multiplexer(addr_width=addr_width, data_width=data_width)
|
self._csr_mux = Multiplexer(addr_width=addr_width, data_width=data_width)
|
||||||
self._csr_mux.add(self.DIVISOR)
|
div_start, _stop = self._csr_mux.add(self.DIVISOR)
|
||||||
self._csr_mux.add(self.SR)
|
sr_start, _stop = self._csr_mux.add(self.SR)
|
||||||
self._csr_mux.add(self.DR)
|
dr_start, _stop = self._csr_mux.add(self.DR)
|
||||||
|
print(f"UART added. DIVISOR 0x{div_start:x}, SR 0x{sr_start:x}, DR 0x{dr_start:x}")
|
||||||
self.bus = self._csr_mux.bus
|
self.bus = self._csr_mux.bus
|
||||||
|
|
||||||
# Actual business logic
|
# Actual business logic
|
||||||
@ -94,6 +95,7 @@ class UART(Elaboratable):
|
|||||||
m.submodules.csr_mux = self._csr_mux
|
m.submodules.csr_mux = self._csr_mux
|
||||||
|
|
||||||
# Hook up divisor to register.
|
# Hook up divisor to register.
|
||||||
|
# TODO do some validation and write a known good value if a dumb value was provided
|
||||||
m.d.comb += self.DIVISOR.r_data.eq(self._serial.divisor)
|
m.d.comb += self.DIVISOR.r_data.eq(self._serial.divisor)
|
||||||
with m.If(self.DIVISOR.w_stb):
|
with m.If(self.DIVISOR.w_stb):
|
||||||
m.d.sync += self._serial.divisor.eq(self.DIVISOR.w_data)
|
m.d.sync += self._serial.divisor.eq(self.DIVISOR.w_data)
|
||||||
|
Loading…
Reference in New Issue
Block a user