Compare commits

...

12 Commits

Author SHA1 Message Date
3bb1134be2 fw: remove some smoltcp socket testing code 2023-06-16 16:33:39 -06:00
afa95aa32e fw: clean up some timer things in main 2023-06-16 16:33:07 -06:00
d4d119dc6e fw: remove vestiges of TCP logging attempt 2023-06-16 16:31:43 -06:00
800c6012f2 fw: formatting 2023-06-16 16:30:49 -06:00
db1ff6761d create python config prompt tool 2023-06-16 16:30:05 -06:00
ea94072e42 fw: reset packet parser after full packet received 2023-06-16 16:29:52 -06:00
1ab57fe316 tweaked container 2023-06-16 16:28:55 -06:00
30d6892477 add privileged flag to devcontainer, to flash from vscode 2023-06-16 14:45:25 -06:00
b57e02f852 Ignore build outputs in docker context to avoid permissions issues 2023-06-16 14:44:38 -06:00
7b53e8be2a fw: clean up and get command processing actually working
Still an issue where connecting to a socket the first time will time
out, unsure what the cause of that or where to start, but can ignore for
now and figure out more when I have more info later
2023-06-16 14:36:44 -06:00
c1e09e3927 update some documentation about the Dockerfile 2023-06-16 14:34:54 -06:00
b0040d8699 fw: re-enable socket (doesn't work though) 2023-06-03 18:23:06 -06:00
18 changed files with 314 additions and 200 deletions

View 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
View File

@ -0,0 +1,3 @@
build/
firmware/target/
gateware/build/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {}
//}
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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