forked from M-Labs/artiq
Implement recording of DMA traces on the core device.
This commit is contained in:
parent
3a1f14c16c
commit
5d3b00cf12
55
artiq/coredevice/dma.py
Normal file
55
artiq/coredevice/dma.py
Normal file
@ -0,0 +1,55 @@
|
||||
from artiq.language.core import syscall, kernel
|
||||
from artiq.language.types import TStr, TNone
|
||||
|
||||
from numpy import int64
|
||||
|
||||
|
||||
@syscall
|
||||
def dma_record_start() -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall
|
||||
def dma_record_stop(name: TStr) -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
class DMARecordContextManager:
|
||||
def __init__(self):
|
||||
self.name = ""
|
||||
self.saved_now_mu = int64(0)
|
||||
|
||||
@kernel
|
||||
def __enter__(self):
|
||||
"""Starts recording a DMA trace. All RTIO operations are redirected to
|
||||
a newly created DMA buffer after this call, and ``now`` is reset to zero."""
|
||||
dma_record_start() # this may raise, so do it before altering now
|
||||
self.saved_now_mu = now_mu()
|
||||
at_mu(0)
|
||||
|
||||
@kernel
|
||||
def __exit__(self, type, value, traceback):
|
||||
"""Stops recording a DMA trace. All recorded RTIO operations are stored
|
||||
in a newly created trace called ``self.name``, and ``now`` is restored
|
||||
to the value it had before ``__enter__`` was called."""
|
||||
dma_record_stop(self.name) # see above
|
||||
at_mu(self.saved_now_mu)
|
||||
|
||||
|
||||
class CoreDMA:
|
||||
"""Core device Direct Memory Access (DMA) driver.
|
||||
|
||||
Gives access to the DMA functionality of the core device.
|
||||
"""
|
||||
|
||||
kernel_invariants = {"core", "recorder"}
|
||||
|
||||
def __init__(self, dmgr, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.recorder = DMARecordContextManager()
|
||||
|
||||
@kernel
|
||||
def record(self, name):
|
||||
"""Returns a context manager that will record a DMA trace called ``name``."""
|
||||
self.recorder.name = name
|
||||
return self.recorder
|
@ -120,6 +120,10 @@ class RTIOOverflow(Exception):
|
||||
"""
|
||||
artiq_builtin = True
|
||||
|
||||
class DMAError(Exception):
|
||||
"""Raised when performing an invalid DMA operation."""
|
||||
artiq_builtin = True
|
||||
|
||||
class DDSError(Exception):
|
||||
"""Raised when attempting to start a DDS batch while already in a batch,
|
||||
when too many commands are batched, and when DDS channel settings are
|
||||
|
@ -20,6 +20,11 @@
|
||||
"module": "artiq.coredevice.cache",
|
||||
"class": "CoreCache"
|
||||
},
|
||||
"core_dma": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dma",
|
||||
"class": "CoreDMA"
|
||||
},
|
||||
"core_dds": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dds",
|
||||
|
@ -105,6 +105,9 @@ static mut API: &'static [(&'static str, *const ())] = &[
|
||||
api!(rtio_input_timestamp = ::rtio::input_timestamp),
|
||||
api!(rtio_input_data = ::rtio::input_data),
|
||||
|
||||
api!(dma_record_start = ::dma_record_start),
|
||||
api!(dma_record_stop = ::dma_record_stop),
|
||||
|
||||
api!(drtio_get_channel_state = ::rtio::drtio_dbg::get_channel_state),
|
||||
api!(drtio_reset_channel_state = ::rtio::drtio_dbg::reset_channel_state),
|
||||
api!(drtio_get_fifo_space = ::rtio::drtio_dbg::get_fifo_space),
|
||||
|
@ -36,11 +36,11 @@ fn recv<R, F: FnOnce(&Message) -> R>(f: F) -> R {
|
||||
|
||||
macro_rules! recv {
|
||||
($p:pat => $e:expr) => {
|
||||
recv(|request| {
|
||||
recv(move |request| {
|
||||
if let $p = request {
|
||||
$e
|
||||
} else {
|
||||
send(&Log(format_args!("unexpected reply: {:?}", request)));
|
||||
send(&Log(format_args!("unexpected reply: {:?}\n", request)));
|
||||
loop {}
|
||||
}
|
||||
})
|
||||
@ -50,7 +50,7 @@ macro_rules! recv {
|
||||
#[no_mangle]
|
||||
#[lang = "panic_fmt"]
|
||||
pub extern fn panic_fmt(args: core::fmt::Arguments, file: &'static str, line: u32) -> ! {
|
||||
send(&Log(format_args!("panic at {}:{}: {}", file, line, args)));
|
||||
send(&Log(format_args!("panic at {}:{}: {}\n", file, line, args)));
|
||||
send(&RunAborted);
|
||||
loop {}
|
||||
}
|
||||
@ -90,6 +90,7 @@ mod api;
|
||||
mod rtio;
|
||||
|
||||
static mut NOW: u64 = 0;
|
||||
static mut LIBRARY: Option<Library<'static>> = None;
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn send_to_core_log(text: CSlice<u8>) {
|
||||
@ -236,6 +237,61 @@ extern fn i2c_read(busno: i32, ack: bool) -> i32 {
|
||||
recv!(&I2cReadReply { data } => data) as i32
|
||||
}
|
||||
|
||||
static mut DMA_RECORDING: bool = false;
|
||||
|
||||
extern fn dma_record_start() {
|
||||
unsafe {
|
||||
if DMA_RECORDING {
|
||||
raise!("DMAError", "DMA is already recording")
|
||||
}
|
||||
|
||||
let library = LIBRARY.as_ref().unwrap();
|
||||
library.rebind(b"rtio_output",
|
||||
dma_record_output as *const () as u32).unwrap();
|
||||
library.rebind(b"rtio_output_wide",
|
||||
dma_record_output_wide as *const () as u32).unwrap();
|
||||
|
||||
DMA_RECORDING = true;
|
||||
send(&DmaRecordStart);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn dma_record_stop(name: CSlice<u8>) {
|
||||
unsafe {
|
||||
if !DMA_RECORDING {
|
||||
raise!("DMAError", "DMA is not recording")
|
||||
}
|
||||
|
||||
let library = LIBRARY.as_ref().unwrap();
|
||||
library.rebind(b"rtio_output",
|
||||
rtio::output as *const () as u32).unwrap();
|
||||
library.rebind(b"rtio_output_wide",
|
||||
rtio::output_wide as *const () as u32).unwrap();
|
||||
|
||||
DMA_RECORDING = false;
|
||||
send(&DmaRecordStop(str::from_utf8(name.as_ref()).unwrap()));
|
||||
}
|
||||
}
|
||||
|
||||
extern fn dma_record_output(timestamp: i64, channel: i32, address: i32, data: i32) {
|
||||
send(&DmaRecordAppend {
|
||||
timestamp: timestamp as u64,
|
||||
channel: channel as u32,
|
||||
address: address as u32,
|
||||
data: &[data as u32]
|
||||
})
|
||||
}
|
||||
|
||||
extern fn dma_record_output_wide(timestamp: i64, channel: i32, address: i32, data: CSlice<i32>) {
|
||||
assert!(data.len() <= 16); // enforce the hardware limit
|
||||
send(&DmaRecordAppend {
|
||||
timestamp: timestamp as u64,
|
||||
channel: channel as u32,
|
||||
address: address as u32,
|
||||
data: unsafe { mem::transmute::<&[i32], &[u32]>(data.as_ref()) }
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn attribute_writeback(typeinfo: *const ()) {
|
||||
struct Attr {
|
||||
offset: usize,
|
||||
@ -248,9 +304,6 @@ unsafe fn attribute_writeback(typeinfo: *const ()) {
|
||||
objects: *const *const ()
|
||||
}
|
||||
|
||||
// artiq_compile'd kernels don't include type information
|
||||
if typeinfo.is_null() { return }
|
||||
|
||||
let mut tys = typeinfo as *const *const Type;
|
||||
while !(*tys).is_null() {
|
||||
let ty = *tys;
|
||||
@ -299,14 +352,21 @@ pub unsafe fn main() {
|
||||
|
||||
let __bss_start = library.lookup(b"__bss_start").unwrap();
|
||||
let _end = library.lookup(b"_end").unwrap();
|
||||
let __modinit__ = library.lookup(b"__modinit__").unwrap();
|
||||
let typeinfo = library.lookup(b"typeinfo");
|
||||
|
||||
LIBRARY = Some(library);
|
||||
|
||||
ptr::write_bytes(__bss_start as *mut u8, 0, (_end - __bss_start) as usize);
|
||||
|
||||
send(&NowInitRequest);
|
||||
recv!(&NowInitReply(now) => NOW = now);
|
||||
(mem::transmute::<u32, fn()>(library.lookup(b"__modinit__").unwrap()))();
|
||||
(mem::transmute::<u32, fn()>(__modinit__))();
|
||||
send(&NowSave(NOW));
|
||||
|
||||
attribute_writeback(library.lookup(b"typeinfo").unwrap_or(0) as *const ());
|
||||
if let Some(typeinfo) = typeinfo {
|
||||
attribute_writeback(typeinfo as *const ());
|
||||
}
|
||||
|
||||
send(&RunFinished);
|
||||
|
||||
|
@ -81,7 +81,6 @@ pub struct Library<'a> {
|
||||
image_sz: usize,
|
||||
strtab: &'a [u8],
|
||||
symtab: &'a [Elf32_Sym],
|
||||
rela: &'a [Elf32_Rela],
|
||||
pltrel: &'a [Elf32_Rela],
|
||||
hash_bucket: &'a [Elf32_Word],
|
||||
hash_chain: &'a [Elf32_Word],
|
||||
@ -132,8 +131,9 @@ impl<'a> Library<'a> {
|
||||
Ok(unsafe { *ptr = value })
|
||||
}
|
||||
|
||||
pub fn rebind(&self, name: &[u8], addr: Elf32_Word) -> Result<(), Error<'a>> {
|
||||
for rela in self.rela.iter().chain(self.pltrel.iter()) {
|
||||
// This is unsafe because it mutates global data (the PLT).
|
||||
pub unsafe fn rebind(&self, name: &[u8], addr: Elf32_Word) -> Result<(), Error<'a>> {
|
||||
for rela in self.pltrel.iter() {
|
||||
match ELF32_R_TYPE(rela.r_info) {
|
||||
R_OR1K_32 | R_OR1K_GLOB_DAT | R_OR1K_JMP_SLOT => {
|
||||
let sym = self.symtab.get(ELF32_R_SYM(rela.r_info) as usize)
|
||||
@ -315,7 +315,6 @@ impl<'a> Library<'a> {
|
||||
image_sz: image.len(),
|
||||
strtab: strtab,
|
||||
symtab: symtab,
|
||||
rela: rela,
|
||||
pltrel: pltrel,
|
||||
hash_bucket: &hash[..nbucket],
|
||||
hash_chain: &hash[nbucket..nbucket + nchain],
|
||||
@ -331,9 +330,8 @@ impl<'a> Library<'a> {
|
||||
// we never write to the memory they refer to, so it's safe.
|
||||
mem::drop(image);
|
||||
|
||||
for r in rela.iter().chain(pltrel.iter()) {
|
||||
library.resolve_rela(r, resolve)?
|
||||
}
|
||||
for r in rela { library.resolve_rela(r, resolve)? }
|
||||
for r in pltrel { library.resolve_rela(r, resolve)? }
|
||||
|
||||
Ok(library)
|
||||
}
|
||||
|
@ -29,6 +29,15 @@ pub enum Message<'a> {
|
||||
|
||||
RtioInitRequest,
|
||||
|
||||
DmaRecordStart,
|
||||
DmaRecordAppend {
|
||||
timestamp: u64,
|
||||
channel: u32,
|
||||
address: u32,
|
||||
data: &'a [u32]
|
||||
},
|
||||
DmaRecordStop(&'a str),
|
||||
|
||||
DrtioChannelStateRequest { channel: u32 },
|
||||
DrtioChannelStateReply { fifo_space: u16, last_timestamp: u64 },
|
||||
DrtioResetChannelStateRequest { channel: u32 },
|
||||
|
@ -41,6 +41,7 @@ mod rtio_mgt;
|
||||
mod urc;
|
||||
mod sched;
|
||||
mod cache;
|
||||
mod rtio_dma;
|
||||
|
||||
mod kernel;
|
||||
mod session;
|
||||
|
58
artiq/firmware/runtime/rtio_dma.rs
Normal file
58
artiq/firmware/runtime/rtio_dma.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use std::vec::Vec;
|
||||
use std::string::String;
|
||||
use std::btree_map::BTreeMap;
|
||||
use std::mem;
|
||||
use proto::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Entry {
|
||||
data: Vec<u8>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Manager {
|
||||
entries: BTreeMap<String, Entry>,
|
||||
recording: Vec<u8>
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn new() -> Manager {
|
||||
Manager {
|
||||
entries: BTreeMap::new(),
|
||||
recording: Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_start(&mut self) {
|
||||
self.recording.clear();
|
||||
}
|
||||
|
||||
pub fn record_append(&mut self, timestamp: u64, channel: u32,
|
||||
address: u32, data: &[u32]) {
|
||||
// See gateware/rtio/dma.py.
|
||||
let length = /*length*/1 + /*channel*/3 + /*timestamp*/8 + /*address*/2 +
|
||||
/*data*/data.len() * 4;
|
||||
let writer = &mut self.recording;
|
||||
io::write_u8(writer, length as u8).unwrap();
|
||||
io::write_u8(writer, (channel >> 24) as u8).unwrap();
|
||||
io::write_u8(writer, (channel >> 16) as u8).unwrap();
|
||||
io::write_u8(writer, (channel >> 8) as u8).unwrap();
|
||||
io::write_u64(writer, timestamp).unwrap();
|
||||
io::write_u16(writer, address as u16).unwrap();
|
||||
for &word in data {
|
||||
io::write_u32(writer, word).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn record_stop(&mut self, name: &str) {
|
||||
let mut recorded = Vec::new();
|
||||
mem::swap(&mut self.recording, &mut recorded);
|
||||
recorded.shrink_to_fit();
|
||||
|
||||
info!("recorded DMA data: {:?}", recorded);
|
||||
|
||||
self.entries.insert(String::from(name), Entry {
|
||||
data: recorded
|
||||
});
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ use std::error::Error;
|
||||
use {config, rtio_mgt, mailbox, rpc_queue, kernel};
|
||||
use logger_artiq::BufferLogger;
|
||||
use cache::Cache;
|
||||
use rtio_dma::Manager as DmaManager;
|
||||
use urc::Urc;
|
||||
use sched::{ThreadHandle, Io};
|
||||
use sched::{TcpListener, TcpStream};
|
||||
@ -34,6 +35,7 @@ fn io_error(msg: &str) -> io::Error {
|
||||
struct Congress {
|
||||
now: u64,
|
||||
cache: Cache,
|
||||
dma_manager: DmaManager,
|
||||
finished_cleanly: Cell<bool>
|
||||
}
|
||||
|
||||
@ -42,6 +44,7 @@ impl Congress {
|
||||
Congress {
|
||||
now: 0,
|
||||
cache: Cache::new(),
|
||||
dma_manager: DmaManager::new(),
|
||||
finished_cleanly: Cell::new(true)
|
||||
}
|
||||
}
|
||||
@ -347,8 +350,7 @@ fn process_host_message(io: &Io,
|
||||
}
|
||||
}
|
||||
|
||||
fn process_kern_message(io: &Io,
|
||||
mut stream: Option<&mut TcpStream>,
|
||||
fn process_kern_message(io: &Io, mut stream: Option<&mut TcpStream>,
|
||||
session: &mut Session) -> io::Result<bool> {
|
||||
kern_recv_notrace(io, |request| {
|
||||
match (request, session.kernel_state) {
|
||||
@ -394,6 +396,19 @@ fn process_kern_message(io: &Io,
|
||||
kern_acknowledge()
|
||||
}
|
||||
|
||||
&kern::DmaRecordStart => {
|
||||
session.congress.dma_manager.record_start();
|
||||
kern_acknowledge()
|
||||
}
|
||||
&kern::DmaRecordAppend { timestamp, channel, address, data } => {
|
||||
session.congress.dma_manager.record_append(timestamp, channel, address, data);
|
||||
kern_acknowledge()
|
||||
}
|
||||
&kern::DmaRecordStop(name) => {
|
||||
session.congress.dma_manager.record_stop(name);
|
||||
kern_acknowledge()
|
||||
}
|
||||
|
||||
&kern::DrtioChannelStateRequest { channel } => {
|
||||
let (fifo_space, last_timestamp) = rtio_mgt::drtio_dbg::get_channel_state(channel);
|
||||
kern_send(io, &kern::DrtioChannelStateReply { fifo_space: fifo_space,
|
||||
|
@ -21,6 +21,12 @@ These drivers are for the core device and the peripherals closely integrated int
|
||||
.. automodule:: artiq.coredevice.dds
|
||||
:members:
|
||||
|
||||
:mod:`artiq.coredevice.dma` module
|
||||
----------------------------------
|
||||
|
||||
.. automodule:: artiq.coredevice.dma
|
||||
:members:
|
||||
|
||||
:mod:`artiq.coredevice.spi` module
|
||||
----------------------------------
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user