Compare commits
12 Commits
64fb1fd3a6
...
3bb1134be2
Author | SHA1 | Date | |
---|---|---|---|
3bb1134be2 | |||
afa95aa32e | |||
d4d119dc6e | |||
800c6012f2 | |||
db1ff6761d | |||
ea94072e42 | |||
1ab57fe316 | |||
30d6892477 | |||
b57e02f852 | |||
7b53e8be2a | |||
c1e09e3927 | |||
b0040d8699 |
13
.devcontainer/devcontainer.json
Normal file
13
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
|
||||||
|
{
|
||||||
|
"name": "sonar_fpga",
|
||||||
|
"build": {
|
||||||
|
// Sets the run context to one level up instead of the .devcontainer folder.
|
||||||
|
"context": "..",
|
||||||
|
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
|
||||||
|
"dockerfile": "../Dockerfile"
|
||||||
|
},
|
||||||
|
|
||||||
|
"privileged": true
|
||||||
|
}
|
3
.dockerignore
Normal file
3
.dockerignore
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
build/
|
||||||
|
firmware/target/
|
||||||
|
gateware/build/
|
33
BUILDING.md
33
BUILDING.md
@ -3,7 +3,38 @@
|
|||||||
All of this is assumed to be on a linux system. Eventually I'll probably make or
|
All of this is assumed to be on a linux system. Eventually I'll probably make or
|
||||||
use some dockerfile or nix flake that has all this installed already.
|
use some dockerfile or nix flake that has all this installed already.
|
||||||
|
|
||||||
## Toolchain Installation
|
The recommended dev flow if you want to make changes is to use VSCode with remote containers,
|
||||||
|
it's the easiest way to get the toolchain up and running, and to fix the issues encountered
|
||||||
|
with migen.
|
||||||
|
|
||||||
|
Recommended extensions:
|
||||||
|
|
||||||
|
- Pylance
|
||||||
|
- rust-analyzer
|
||||||
|
|
||||||
|
## Building with docker
|
||||||
|
|
||||||
|
Easiest way is to build and use the docker container:
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -r arvp_sonar:latest ./
|
||||||
|
# Privileged lets you access USB devices inside the container
|
||||||
|
docker run --privileged --rm -it -v /path/to/repository:/code arvp_sonar:latest
|
||||||
|
|
||||||
|
# These commands are run inside the container
|
||||||
|
cd /code/gateware
|
||||||
|
python3 litex_main.py --build # Build the bitstream
|
||||||
|
|
||||||
|
# To write to the flash storage
|
||||||
|
# TODO not sure how to use this command
|
||||||
|
ecpdap write
|
||||||
|
# To program the FPGA once
|
||||||
|
ecpdap program build/sonar/gateware/sonar.bit --freq 10M
|
||||||
|
```
|
||||||
|
|
||||||
|
This will generate a bitstream in `gateware/build/sonar
|
||||||
|
|
||||||
|
## Toolchain Manual Installation
|
||||||
|
|
||||||
### Trellis and FPGA toolchain
|
### Trellis and FPGA toolchain
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ FROM ubuntu:22.04
|
|||||||
WORKDIR /litex
|
WORKDIR /litex
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN apt-get update && apt-get install -y wget curl python3 python3-pip git gcc-riscv64-linux-gnu ninja-build
|
RUN apt-get update && apt-get install -y wget curl python3 python3-pip python3-venv git gcc-riscv64-linux-gnu ninja-build
|
||||||
# TODO consolidate curl and wget
|
# TODO consolidate curl and wget
|
||||||
RUN pip3 install numpy matplotlib
|
RUN pip3 install numpy matplotlib
|
||||||
|
|
||||||
@ -33,6 +33,8 @@ COPY migen.patch /litex/migen/migen.patch
|
|||||||
RUN cd /litex/migen && git apply /litex/migen/migen.patch
|
RUN cd /litex/migen && git apply /litex/migen/migen.patch
|
||||||
RUN pip install --editable /litex/migen
|
RUN pip install --editable /litex/migen
|
||||||
|
|
||||||
|
# TODO remove unnecessary tools
|
||||||
|
|
||||||
# Delete package cache to keep size small
|
# Delete package cache to keep size small
|
||||||
RUN apt-get clean
|
RUN apt-get clean
|
||||||
|
|
||||||
|
@ -109,3 +109,8 @@ Do I have ecpdap installed in two places and one of them doesn't work maybe?
|
|||||||
|
|
||||||
I do have two versions installed. one from oss-cad-suite and one by building it
|
I do have two versions installed. one from oss-cad-suite and one by building it
|
||||||
manually.
|
manually.
|
||||||
|
|
||||||
|
### Pip install makes UNKNOWN package in docker
|
||||||
|
|
||||||
|
This is some weird conflict between pip's setuptools and the system setuptools,
|
||||||
|
to solve just set everything up in a venv.
|
||||||
|
@ -20,3 +20,4 @@ features = ["medium-ethernet", "proto-ipv4", "socket-tcp", "socket-icmp", "defmt
|
|||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
opt-level = "s"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/sh
|
#!/usr/bin/sh
|
||||||
DEFMT_LOG=trace cargo build --release
|
DEFMT_LOG=debug cargo build --release
|
||||||
if [ $? -ne 0 ]
|
if [ $? -ne 0 ]
|
||||||
then
|
then
|
||||||
exit $?
|
exit $?
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
MEMORY
|
MEMORY
|
||||||
{
|
{
|
||||||
RAM : ORIGIN = 0x10000000, LENGTH = 64K
|
RAM : ORIGIN = 0x10000000, LENGTH = 64K
|
||||||
FLASH : ORIGIN = 0x00000000, LENGTH = 80K
|
FLASH : ORIGIN = 0x00000000, LENGTH = 100K
|
||||||
}
|
}
|
||||||
|
|
||||||
REGION_ALIAS("REGION_TEXT", FLASH);
|
REGION_ALIAS("REGION_TEXT", FLASH);
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
use smoltcp::socket::tcp::{Socket, State};
|
use smoltcp::socket::tcp::{Socket, State};
|
||||||
|
|
||||||
use crate::proto::{
|
use crate::proto::{
|
||||||
serialize_response_error, serialize_response_value, PacketParser, ResponsePacket, Settings, ErrorCodes
|
serialize_response_error, serialize_response_value, ErrorCodes, PacketParser, ResponsePacket,
|
||||||
|
Settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct CommandInterface {
|
pub struct CommandInterface {
|
||||||
parser: PacketParser,
|
parser: PacketParser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const COMMAND_PORT: u16 = 2000;
|
||||||
|
|
||||||
impl CommandInterface {
|
impl CommandInterface {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -22,8 +25,16 @@ impl CommandInterface {
|
|||||||
// provides a buffer of length 0 when there is nothing to receive, so we
|
// provides a buffer of length 0 when there is nothing to receive, so we
|
||||||
// are good there. We just reset on any smoltcp errors.
|
// are good there. We just reset on any smoltcp errors.
|
||||||
|
|
||||||
// TODO should put packet parsing behind a fn with the smoltcp result, then on smoltcp error
|
if !sock.is_open() {
|
||||||
// we reconfigure here instead of calling it 4 times
|
defmt::debug!("Socket has been closed");
|
||||||
|
// Socket has been closed, some error has occured
|
||||||
|
sock.listen(COMMAND_PORT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sock.can_recv() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Try and parse a packet
|
// Try and parse a packet
|
||||||
let res = sock.recv(|rx_buf| {
|
let res = sock.recv(|rx_buf| {
|
||||||
@ -32,47 +43,56 @@ impl CommandInterface {
|
|||||||
(processed_bytes, res.ok())
|
(processed_bytes, res.ok())
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defmt::debug!("Received data");
|
||||||
|
|
||||||
// Check for socket errors
|
// Check for socket errors
|
||||||
let packet = match res {
|
let packet = match res {
|
||||||
|
// We got a packet! unwrap it
|
||||||
Ok(Some(packet)) => packet,
|
Ok(Some(packet)) => packet,
|
||||||
// No packet to process
|
// No packet to process, move on
|
||||||
Ok(None) => return,
|
Ok(None) => return,
|
||||||
// Any socket error means we just want to drop the connection and re-connect
|
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
Self::reestablish_socket(sock);
|
defmt::debug!("rx err");
|
||||||
|
sock.abort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
defmt::debug!("Packet rx");
|
||||||
|
|
||||||
// Check that setting is valid
|
// Check that setting is valid
|
||||||
let setting = match Settings::try_from(packet.setting) {
|
let setting = match Settings::try_from(packet.setting) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(()) => {
|
Err(()) => {
|
||||||
// Write out an error packet
|
// Write out an error packet, we'll just ignore errors
|
||||||
let result = sock.send(|tx_buf| {
|
let _ = sock.send(|tx_buf| {
|
||||||
if tx_buf.len() < 8 {
|
if tx_buf.len() < 8 {
|
||||||
|
// Just drop the packet if we don't have buffer available
|
||||||
return (0, ());
|
return (0, ());
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = serialize_response_error(packet.setting, ErrorCodes::InvalidSetting);
|
let response =
|
||||||
|
serialize_response_error(packet.setting, ErrorCodes::InvalidSetting);
|
||||||
&tx_buf[0..8].copy_from_slice(&response);
|
&tx_buf[0..8].copy_from_slice(&response);
|
||||||
return (8, ());
|
return (8, ());
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Err(_) = result {
|
|
||||||
// TX error, close socket and reconnect
|
|
||||||
Self::reestablish_socket(sock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
defmt::debug!(
|
||||||
|
"Valid packet: {:?}, is_write: {}, value: {}",
|
||||||
|
packet.setting,
|
||||||
|
packet.is_write,
|
||||||
|
packet.value
|
||||||
|
);
|
||||||
|
|
||||||
// TODO validate setting values
|
// TODO validate setting values
|
||||||
|
|
||||||
// TODO handle actually changing/getting settings in all the ways
|
// TODO handle actually changing/getting settings in all the ways
|
||||||
// For temp testing, return values sent
|
// For temp testing, return values sent
|
||||||
match sock.send(|tx_buf| {
|
let res = sock.send(|tx_buf| {
|
||||||
if tx_buf.len() < 8 {
|
if tx_buf.len() < 8 {
|
||||||
// Since this is a low-BW configuration socket, we assume we have space, and drop
|
// Since this is a low-BW configuration socket, we assume we have space, and drop
|
||||||
// the response otherwise. If this is an issue, may re-think this and queue commands but
|
// the response otherwise. If this is an issue, may re-think this and queue commands but
|
||||||
@ -82,16 +102,11 @@ impl CommandInterface {
|
|||||||
let response = serialize_response_value(setting, packet.value);
|
let response = serialize_response_value(setting, packet.value);
|
||||||
&tx_buf[0..8].copy_from_slice(&response);
|
&tx_buf[0..8].copy_from_slice(&response);
|
||||||
return (8, ());
|
return (8, ());
|
||||||
}) {
|
});
|
||||||
Ok(()) => (),
|
|
||||||
Err(_) => {
|
|
||||||
Self::reestablish_socket(sock);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reestablish_socket(sock: &mut Socket) {
|
if res.is_err() {
|
||||||
sock.abort();
|
defmt::debug!("tx err");
|
||||||
sock.listen(2000);
|
sock.abort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,9 +38,6 @@ const SLOT_LEN: u32 = 2048;
|
|||||||
|
|
||||||
use crate::{busy_wait, read_reg, write_reg};
|
use crate::{busy_wait, read_reg, write_reg};
|
||||||
|
|
||||||
use crate::uart::AmlibUart;
|
|
||||||
use core::fmt::Write;
|
|
||||||
|
|
||||||
pub struct LiteEthDevice {
|
pub struct LiteEthDevice {
|
||||||
csr_addr: u32,
|
csr_addr: u32,
|
||||||
ethmac_addr: u32,
|
ethmac_addr: u32,
|
||||||
@ -72,7 +69,10 @@ impl LiteEthDevice {
|
|||||||
write_reg(csr_addr + ETHMAC_SRAM_WRITER_EV_ENABLE, 0u32);
|
write_reg(csr_addr + ETHMAC_SRAM_WRITER_EV_ENABLE, 0u32);
|
||||||
|
|
||||||
// Return a new device
|
// Return a new device
|
||||||
Some(Self { csr_addr, ethmac_addr })
|
Some(Self {
|
||||||
|
csr_addr,
|
||||||
|
ethmac_addr,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,13 +199,9 @@ impl smoltcp::phy::RxToken for LiteEthRxToken {
|
|||||||
F: FnOnce(&mut [u8]) -> R,
|
F: FnOnce(&mut [u8]) -> R,
|
||||||
{
|
{
|
||||||
// Read the slot number
|
// Read the slot number
|
||||||
let slot = unsafe {
|
let slot = unsafe { read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_SLOT) };
|
||||||
read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_SLOT)
|
|
||||||
};
|
|
||||||
// Read the available length
|
// Read the available length
|
||||||
let len = unsafe {
|
let len = unsafe { read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_LENGTH) };
|
||||||
read_reg::<u32>(self.csr_addr + ETHMAC_SRAM_WRITER_LENGTH)
|
|
||||||
};
|
|
||||||
|
|
||||||
let rx_slot_addr: u32 = self.ethmac_addr + slot * SLOT_LEN;
|
let rx_slot_addr: u32 = self.ethmac_addr + slot * SLOT_LEN;
|
||||||
let rx_slot: &mut [u8] =
|
let rx_slot: &mut [u8] =
|
||||||
|
@ -1,45 +0,0 @@
|
|||||||
//! 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(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
use core::{fmt::Write, any::Any};
|
use core::{any::Any, fmt::Write};
|
||||||
|
|
||||||
use defmt;
|
use defmt;
|
||||||
|
|
||||||
use crate::uart::AmlibUart;
|
use crate::uart::LitexUart;
|
||||||
use core::arch::asm;
|
use core::arch::asm;
|
||||||
|
|
||||||
#[defmt::global_logger]
|
#[defmt::global_logger]
|
||||||
@ -22,17 +22,14 @@ unsafe impl defmt::Logger for DefmtLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn write(bytes: &[u8]) {
|
unsafe fn write(bytes: &[u8]) {
|
||||||
//static mut UART: Option<AmlibUart> = None;
|
static mut UART: Option<LitexUart> = None;
|
||||||
//if UART.is_none() {
|
if UART.is_none() {
|
||||||
// UART = Some(AmlibUart::new(0x0200_0040));
|
UART = Some(LitexUart::new(0xf000_4000));
|
||||||
//}
|
}
|
||||||
|
|
||||||
|
let mut dev = UART.unwrap();
|
||||||
//let mut dev = UART.unwrap();
|
for byte in bytes {
|
||||||
////writeln!(dev, "a").unwrap();
|
while let Err(_) = dev.try_put_char(*byte) {}
|
||||||
////writeln!(dev, "length: {}", bytes.len());
|
}
|
||||||
//for byte in bytes {
|
|
||||||
// while let Err(_) = dev.try_put_char(*byte) {}
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
// TODO remove
|
// TODO remove
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
@ -15,46 +14,29 @@ use core::{
|
|||||||
use embedded_hal::prelude::{_embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write};
|
use embedded_hal::prelude::{_embedded_hal_blocking_i2c_Read, _embedded_hal_blocking_i2c_Write};
|
||||||
use mcp4726::Status;
|
use mcp4726::Status;
|
||||||
use riscv_rt::entry;
|
use riscv_rt::entry;
|
||||||
use smoltcp::socket::{Socket, self};
|
use smoltcp::socket::{self, Socket};
|
||||||
|
use smoltcp::time::Duration;
|
||||||
use smoltcp::wire::{IpAddress, Ipv4Address};
|
use smoltcp::wire::{IpAddress, Ipv4Address};
|
||||||
use smoltcp::{
|
use smoltcp::{
|
||||||
iface::{SocketSet, SocketStorage},
|
iface::{SocketSet, SocketStorage},
|
||||||
time::Instant,
|
|
||||||
wire::HardwareAddress,
|
|
||||||
socket::tcp::Socket as TcpSocket,
|
socket::tcp::Socket as TcpSocket,
|
||||||
socket::tcp::SocketBuffer,
|
socket::tcp::SocketBuffer,
|
||||||
|
time::Instant,
|
||||||
|
wire::HardwareAddress,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod command_interface;
|
||||||
mod eth;
|
mod eth;
|
||||||
mod i2c;
|
mod i2c;
|
||||||
mod mcp4726;
|
|
||||||
mod uart;
|
|
||||||
mod litex_uart;
|
|
||||||
mod logging;
|
mod logging;
|
||||||
|
mod mcp4726;
|
||||||
mod proto;
|
mod proto;
|
||||||
mod command_interface;
|
mod uart;
|
||||||
|
|
||||||
const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0];
|
const MAC: [u8; 6] = [0xA0, 0xBB, 0xCC, 0xDD, 0xEE, 0xF0];
|
||||||
|
|
||||||
static mut SECONDS: u32 = 0;
|
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
|
// use `main` as the entry point of this application
|
||||||
// `main` is not allowed to return
|
// `main` is not allowed to return
|
||||||
#[entry]
|
#[entry]
|
||||||
@ -69,13 +51,10 @@ fn main() -> ! {
|
|||||||
// }
|
// }
|
||||||
//};
|
//};
|
||||||
let blink_period = 10_000_000u32;
|
let blink_period = 10_000_000u32;
|
||||||
let mut uart = litex_uart::LiteXUart::new(0xf000_4000);
|
|
||||||
writeln!(uart, "uart init");
|
|
||||||
|
|
||||||
// enable timer
|
// enable timer
|
||||||
|
|
||||||
let mut device = unsafe { eth::LiteEthDevice::try_init(0xf000_0800, 0x8000_0000).unwrap() };
|
let mut device = unsafe { eth::LiteEthDevice::try_init(0xf000_0800, 0x8000_0000).unwrap() };
|
||||||
writeln!(uart, "eth init");
|
|
||||||
|
|
||||||
use smoltcp::wire::{EthernetAddress, HardwareAddress};
|
use smoltcp::wire::{EthernetAddress, HardwareAddress};
|
||||||
let mut config = smoltcp::iface::Config::default();
|
let mut config = smoltcp::iface::Config::default();
|
||||||
@ -101,59 +80,60 @@ fn main() -> ! {
|
|||||||
let mut socket_storage = [SocketStorage::EMPTY; 4];
|
let mut socket_storage = [SocketStorage::EMPTY; 4];
|
||||||
let mut socket_set = SocketSet::new(&mut socket_storage[..]);
|
let mut socket_set = SocketSet::new(&mut socket_storage[..]);
|
||||||
|
|
||||||
//let mut tx_storage = [0u8; 128];
|
let mut tx_storage = [0u8; 64];
|
||||||
//let mut rx_storage = [0u8; 128];
|
let mut rx_storage = [0u8; 64];
|
||||||
//let mut tx_buf = SocketBuffer::new(&mut tx_storage[..]);
|
let mut tx_buf = SocketBuffer::new(&mut tx_storage[..]);
|
||||||
//let mut rx_buf = SocketBuffer::new(&mut rx_storage[..]);
|
let mut rx_buf = SocketBuffer::new(&mut rx_storage[..]);
|
||||||
//let mut command_socket = socket_set.add(TcpSocket::new(tx_buf, rx_buf));
|
let mut command_socket = socket_set.add(TcpSocket::new(tx_buf, rx_buf));
|
||||||
|
// Set a keepalive on the socket to handle unexpected client disconnects
|
||||||
|
{
|
||||||
|
let mut sock = socket_set.get_mut::<TcpSocket>(command_socket);
|
||||||
|
// TODO these values are obscene, should fix the underlying bug
|
||||||
|
sock.set_keep_alive(Some(Duration::from_secs(2)));
|
||||||
|
sock.set_timeout(Some(Duration::from_secs(10)))
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_blink: u32 = 0;
|
let mut last_blink: u32 = 0;
|
||||||
let mut toggle = false;
|
let mut toggle = false;
|
||||||
//defmt::info!("Done setup");
|
//defmt::info!("Done setup");
|
||||||
|
|
||||||
|
// Set up timer for polling events
|
||||||
unsafe {
|
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
|
// Timer stuff
|
||||||
write_reg(0xf000_3808, 0u32); // Disable timer
|
write_reg(0xf000_3808, 0u32); // Disable timer
|
||||||
write_reg(0xf000_3800, 0u32); // Set LOAD value
|
write_reg(0xf000_3800, 0u32); // Set LOAD value
|
||||||
write_reg(0xf000_3804, 60_000_000u32); // Set RELOAD value
|
write_reg(0xf000_3804, 60_000_000u32); // Set RELOAD value
|
||||||
write_reg(0xf000_3808, 1u32); // Enable timer
|
write_reg(0xf000_3808, 1u32); // Enable timer
|
||||||
|
|
||||||
// Enable timer event
|
|
||||||
//write_reg(0xf000_381c, 1u32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//let mut cmd = command_interface::CommandInterface::new();
|
let mut cmd = command_interface::CommandInterface::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let now = millis();
|
let now = millis();
|
||||||
if now - last_blink > 1000 {
|
// TODO the need for the second check screams something is unsound somewhere
|
||||||
|
if now - last_blink > 1000 && now > last_blink {
|
||||||
last_blink = now;
|
last_blink = now;
|
||||||
toggle = !toggle;
|
toggle = !toggle;
|
||||||
write_led(if toggle { 1 } else { 0 });
|
write_led(if toggle { 1 } else { 0 });
|
||||||
|
|
||||||
let val: u32 = unsafe {read_reg(0x8000_2000)};
|
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) {
|
|
||||||
//cmd.run(socket_set.get_mut(command_socket));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO I think the timer might actually stop until the event is cleared? this may pose
|
||||||
|
// problems, might explain why moving this above the smoltcp stuff "broke" things
|
||||||
handle_timer_event();
|
handle_timer_event();
|
||||||
|
|
||||||
|
iface.poll(Instant::from_millis(now), &mut device, &mut socket_set);
|
||||||
|
|
||||||
|
// TODO first connection to a socket takes a while to establish, and can time out, why?
|
||||||
|
cmd.run(socket_set.get_mut(command_socket));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_timer_event() {
|
fn handle_timer_event() {
|
||||||
unsafe {
|
unsafe {
|
||||||
if read_reg::<u32>(0xf000_3818) == 0 {
|
if read_reg::<u32>(0xf000_3818) == 0 {
|
||||||
|
// No event yet, continue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,19 +141,14 @@ fn handle_timer_event() {
|
|||||||
write_reg(0xf000_3818, 1u32);
|
write_reg(0xf000_3818, 1u32);
|
||||||
SECONDS += 1;
|
SECONDS += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn busy_wait(ms: u32) {
|
fn busy_wait(ms: u32) {
|
||||||
//let start = millis();
|
let start = millis();
|
||||||
//while millis() - start < ms {
|
while millis() - start < ms {
|
||||||
// unsafe {
|
unsafe {
|
||||||
// asm!("nop");
|
asm!("nop");
|
||||||
// }
|
}
|
||||||
//}
|
|
||||||
|
|
||||||
for i in 0..ms*20_000 {
|
|
||||||
unsafe {asm!("nop");}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
//! because the crc python package had it as well.
|
//! because the crc python package had it as well.
|
||||||
//!
|
//!
|
||||||
//! The 4 bytes of data are little-endian.
|
//! The 4 bytes of data are little-endian.
|
||||||
//!
|
//!
|
||||||
//! For the settings that can be set, see [Settings].
|
//! For the settings that can be set, see [Settings].
|
||||||
//!
|
//!
|
||||||
//! ## Sending a command
|
//! ## Sending a command
|
||||||
@ -194,11 +194,13 @@ impl PacketParser {
|
|||||||
// Check CRC
|
// Check CRC
|
||||||
7 => {
|
7 => {
|
||||||
if self.crc.get_crc() == byte {
|
if self.crc.get_crc() == byte {
|
||||||
return Ok(CommandPacket {
|
let out = CommandPacket {
|
||||||
is_write: self.is_write,
|
is_write: self.is_write,
|
||||||
setting: self.setting,
|
setting: self.setting,
|
||||||
value: self.value,
|
value: self.value,
|
||||||
});
|
};
|
||||||
|
self.reset();
|
||||||
|
return Ok(out);
|
||||||
} else {
|
} else {
|
||||||
self.reset();
|
self.reset();
|
||||||
return Err(Error::BadCrc);
|
return Err(Error::BadCrc);
|
||||||
|
@ -2,18 +2,13 @@
|
|||||||
|
|
||||||
use core::fmt::Write;
|
use core::fmt::Write;
|
||||||
/// TODO repr(C) a register bank, and add instances
|
/// TODO repr(C) a register bank, and add instances
|
||||||
///
|
|
||||||
use core::ptr::{read_volatile, write_volatile};
|
use core::ptr::{read_volatile, write_volatile};
|
||||||
|
|
||||||
// TODO these offsets may be wrong. I'm still unsure about CSR semantics
|
const REG_RXTX: u32 = 0x00;
|
||||||
const REG_DIVISOR_OFFSET: u32 = 0;
|
const REG_TXFULL: u32 = 0x04;
|
||||||
const REG_SR_OFFSET: u32 = 2;
|
const REG_RXEMPTY: u32 = 0x08;
|
||||||
const REG_DR_OFFSET: u32 = 3;
|
const REG_TXEMPTY: u32 = 0x18;
|
||||||
|
const REG_RXFULL: u32 = 0x1c;
|
||||||
const FLAG_SR_TX_FULL: u8 = 1 << 0;
|
|
||||||
const FLAG_SR_TX_EMPTY: u8 = 1 << 1;
|
|
||||||
const FLAG_SR_RX_FULL: u8 = 1 << 2;
|
|
||||||
const FLAG_SR_RX_EMPTY: u8 = 1 << 3;
|
|
||||||
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
TxFull,
|
TxFull,
|
||||||
@ -22,41 +17,43 @@ pub enum Error {
|
|||||||
|
|
||||||
// Hacky derive, shouldn't exist but it's fine :tm:
|
// Hacky derive, shouldn't exist but it's fine :tm:
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct AmlibUart {
|
pub struct LitexUart {
|
||||||
base_addr: u32,
|
base_addr: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AmlibUart {
|
impl LitexUart {
|
||||||
pub fn new(base_addr: u32) -> Self {
|
pub fn new(base_addr: u32) -> Self {
|
||||||
Self { base_addr }
|
Self { base_addr }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_status(&mut self) -> u8 {
|
unsafe fn read_reg(&mut self, offset: u32) -> u32 {
|
||||||
unsafe { read_volatile((self.base_addr + REG_SR_OFFSET) as *const u8) }
|
read_volatile((self.base_addr + offset) as *const u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_get_char(&mut self) -> Result<u8, Error> {
|
pub fn try_get_char(&mut self) -> Result<u8, Error> {
|
||||||
if self.read_status() & FLAG_SR_RX_EMPTY != 0 {
|
unsafe {
|
||||||
return Err(Error::RxEmpty);
|
if self.read_reg(REG_RXEMPTY) != 0 {
|
||||||
}
|
return Err(Error::RxEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
unsafe { Ok(read_volatile((self.base_addr + REG_DR_OFFSET) as *const u8)) }
|
Ok(read_volatile((self.base_addr + REG_RXTX) as *mut u8))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_put_char(&mut self, c: u8) -> Result<(), Error> {
|
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 {
|
unsafe {
|
||||||
write_volatile((self.base_addr + REG_DR_OFFSET) as *mut u8, c);
|
if self.read_reg(REG_TXFULL) != 0 {
|
||||||
|
return Err(Error::TxFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_volatile((self.base_addr + REG_RXTX) as *mut u8, c);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocking implementation of write
|
// Blocking implementation of write
|
||||||
impl Write for AmlibUart {
|
impl Write for LitexUart {
|
||||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||||
for b in s.as_bytes() {
|
for b in s.as_bytes() {
|
||||||
// It's okay to loop on this because we'll always clear the buffer
|
// It's okay to loop on this because we'll always clear the buffer
|
||||||
|
@ -8,7 +8,7 @@ authors = [
|
|||||||
{ name = "David Lenfesty", email = "lenfesty@ualberta.ca" }
|
{ name = "David Lenfesty", email = "lenfesty@ualberta.ca" }
|
||||||
]
|
]
|
||||||
requires-python = ">=3.9"
|
requires-python = ">=3.9"
|
||||||
dependencies = ["crc>=4.2"]
|
dependencies = ["crc>=4.2", "prompt-toolkit>=3.0"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
# Export CLI to configure FPGA
|
# Export CLI to configure FPGA
|
||||||
|
@ -1,17 +1,134 @@
|
|||||||
IP = "192.168.88.69"
|
|
||||||
PORT = 2000
|
|
||||||
|
|
||||||
import socket
|
import socket
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
|
||||||
|
from prompt_toolkit import PromptSession
|
||||||
|
from prompt_toolkit.completion import NestedCompleter
|
||||||
|
from prompt_toolkit.validation import Validator, ValidationError
|
||||||
|
|
||||||
from .command_packets import CommandPacket, Settings, PacketParser
|
from .command_packets import CommandPacket, Settings, PacketParser
|
||||||
|
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
"trigger_threshold": Settings.TriggerThreshold,
|
||||||
|
"trigger_period": Settings.TriggerPeriod,
|
||||||
|
"decay_value": Settings.DecayValue,
|
||||||
|
"decay_period": Settings.DecayPeriod,
|
||||||
|
"gain": Settings.Gain,
|
||||||
|
"center_frequency": Settings.CenterFreq,
|
||||||
|
"sampling_enabled": Settings.SamplingEnabled,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
commands = {
|
||||||
|
"set": set(settings.keys()),
|
||||||
|
"get": set(settings.keys()),
|
||||||
|
"quit": None,
|
||||||
|
"help": None,
|
||||||
|
"?": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CommandValidator(Validator):
|
||||||
|
def validate(self, document):
|
||||||
|
command = document.text.strip().split()
|
||||||
|
if len(command) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
if command[0] not in commands.keys():
|
||||||
|
raise ValidationError(message="Unrecognized command")
|
||||||
|
|
||||||
|
if command[0] not in ["set", "get"]:
|
||||||
|
if len(command) > 1:
|
||||||
|
raise ValidationError(message="Too many arguments")
|
||||||
|
else:
|
||||||
|
if command[0] == "set":
|
||||||
|
num_args = 3
|
||||||
|
else:
|
||||||
|
num_args = 2
|
||||||
|
|
||||||
|
if len(command) < num_args:
|
||||||
|
raise ValidationError(message="Not enough arguments")
|
||||||
|
|
||||||
|
if len(command) > num_args:
|
||||||
|
raise ValidationError(message="Too many arguments")
|
||||||
|
|
||||||
|
if command[1] not in settings:
|
||||||
|
raise ValidationError(message="Unrecognized setting")
|
||||||
|
|
||||||
|
if num_args > 2:
|
||||||
|
try:
|
||||||
|
int(command[2])
|
||||||
|
except ValueError:
|
||||||
|
raise ValidationError(message="Setting value not an integer")
|
||||||
|
|
||||||
|
|
||||||
|
def print_help():
|
||||||
|
print("==== Sonar Configuration CLI ====")
|
||||||
|
print("Commands:")
|
||||||
|
print("\tset <setting> <value> - Sets the integer value of a setting")
|
||||||
|
print("\tget <setting> - gets the integer value of a setting")
|
||||||
|
print("\tquit - exit this prompt")
|
||||||
|
print("\thelp, ? - this help")
|
||||||
|
print()
|
||||||
|
print("Available Settings:")
|
||||||
|
for setting in settings.keys():
|
||||||
|
print(f"\t{setting}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
def send_command(sock, cmd: CommandPacket):
|
||||||
|
"""Sends a command, and pretty-prints the response"""
|
||||||
|
sock.send(cmd.serialize())
|
||||||
|
|
||||||
|
data = sock.recv(16)
|
||||||
|
parser = PacketParser()
|
||||||
|
data, packet = parser.parse_bytearray(data)
|
||||||
|
if packet is not None:
|
||||||
|
print(packet)
|
||||||
|
else:
|
||||||
|
print("No response received! Must be a bug...")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
args = ArgumentParser(prog="sonar_config", description="Configuration utility for ARVP sonar")
|
||||||
sock.connect((IP, PORT))
|
args.add_argument("IP", type=str, help="IP of sonar system")
|
||||||
sock.send(CommandPacket(False, Settings.Gain, 125).serialize())
|
args.add_argument("--port", "-p", type=int, help="Port of configuration socket", default=2000)
|
||||||
|
args = args.parse_args()
|
||||||
|
|
||||||
data = sock.recv(1024)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.connect((args.IP, args.port))
|
||||||
|
|
||||||
parser = PacketParser()
|
# Start prompt
|
||||||
data, packet = parser.parse_bytearray(data)
|
completer = NestedCompleter.from_nested_dict(commands)
|
||||||
if packet is not None:
|
validator = CommandValidator()
|
||||||
print(packet)
|
session = PromptSession("> ", completer=completer, validator=validator, validate_while_typing=True)
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
command = session.prompt().strip().split()
|
||||||
|
# We assume the command is a valid command at this point,
|
||||||
|
# as long as the validator is doing it's job. Don't
|
||||||
|
# try and validate anything here!
|
||||||
|
|
||||||
|
if command[0] in ["help", "?"]:
|
||||||
|
print_help()
|
||||||
|
elif command[0] == "get":
|
||||||
|
send_command(sock, CommandPacket(False, settings[command[1]], 0))
|
||||||
|
elif command[0] == "set":
|
||||||
|
send_command(sock, CommandPacket(True, settings[command[1]], int(command[2])))
|
||||||
|
elif command[0] == "quit":
|
||||||
|
break
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
# Ignore the current prompt, move on
|
||||||
|
pass
|
||||||
|
|
||||||
|
except EOFError:
|
||||||
|
# Ctrl-D exits
|
||||||
|
break
|
||||||
|
|
||||||
|
print("Disconnecting from socket...", end="")
|
||||||
|
# TODO this doesn't really close the socket the way I want it to...
|
||||||
|
# unsure if it's a FW issue, python issue, or weird docker interaction
|
||||||
|
sock.shutdown(socket.SHUT_RDWR)
|
||||||
|
sock.close()
|
||||||
|
print(" Goodbye!")
|
||||||
|
@ -2,7 +2,7 @@ from crc import Calculator, Crc8
|
|||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
from struct import pack
|
from struct import pack, unpack
|
||||||
|
|
||||||
|
|
||||||
PKT_START_SEQUENCE = [0xDE, 0xAD]
|
PKT_START_SEQUENCE = [0xDE, 0xAD]
|
||||||
@ -103,6 +103,11 @@ class PacketParser:
|
|||||||
if len(self.packet_data) < 7:
|
if len(self.packet_data) < 7:
|
||||||
raise Exception("Invalid amount of packet data received!")
|
raise Exception("Invalid amount of packet data received!")
|
||||||
|
|
||||||
|
is_error = self.packet_data[2] & 0x80 != 0
|
||||||
|
setting = Settings(self.packet_data[2] & 0x7F)
|
||||||
|
value = unpack("<I", self.packet_data[3:7])
|
||||||
|
|
||||||
|
return ResponsePacket(is_error, setting, value)
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
self.packet_data = bytearray()
|
self.packet_data = bytearray()
|
||||||
|
Loading…
Reference in New Issue
Block a user