Implement DMA playback.

This commit is contained in:
whitequark 2017-02-28 21:28:27 +00:00
parent 4107938fd8
commit a4ece19614
8 changed files with 183 additions and 19 deletions

View File

@ -1,5 +1,5 @@
from artiq.language.core import syscall, kernel from artiq.language.core import syscall, kernel
from artiq.language.types import TStr, TNone from artiq.language.types import TInt64, TStr, TNone
from numpy import int64 from numpy import int64
@ -8,11 +8,18 @@ from numpy import int64
def dma_record_start() -> TNone: def dma_record_start() -> TNone:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall @syscall
def dma_record_stop(name: TStr) -> TNone: def dma_record_stop(name: TStr) -> TNone:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall
def dma_erase(name: TStr) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dma_playback(timestamp: TInt64, name: TStr) -> TNone:
raise NotImplementedError("syscall not simulated")
class DMARecordContextManager: class DMARecordContextManager:
def __init__(self): def __init__(self):
@ -50,6 +57,19 @@ class CoreDMA:
@kernel @kernel
def record(self, name): def record(self, name):
"""Returns a context manager that will record a DMA trace called ``name``.""" """Returns a context manager that will record a DMA trace called ``name``.
Any previously recorded trace with the same name is overwritten.
The trace will persist across kernel switches."""
self.recorder.name = name self.recorder.name = name
return self.recorder return self.recorder
@kernel
def erase(self, name):
"""Removes the DMA trace with the given name from storage."""
dma_erase(name)
@kernel
def replay(self, name):
"""Replays a previously recorded DMA trace. This function blocks until
the entire trace is submitted to the RTIO FIFOs."""
dma_playback(now_mu(), name)

View File

@ -107,6 +107,8 @@ static mut API: &'static [(&'static str, *const ())] = &[
api!(dma_record_start = ::dma_record_start), api!(dma_record_start = ::dma_record_start),
api!(dma_record_stop = ::dma_record_stop), api!(dma_record_stop = ::dma_record_stop),
api!(dma_erase = ::dma_erase),
api!(dma_playback = ::dma_playback),
api!(drtio_get_channel_state = ::rtio::drtio_dbg::get_channel_state), api!(drtio_get_channel_state = ::rtio::drtio_dbg::get_channel_state),
api!(drtio_reset_channel_state = ::rtio::drtio_dbg::reset_channel_state), api!(drtio_reset_channel_state = ::rtio::drtio_dbg::reset_channel_state),

View File

@ -17,6 +17,7 @@ extern crate amp;
use core::{mem, ptr, slice, str}; use core::{mem, ptr, slice, str};
use std::io::Cursor; use std::io::Cursor;
use cslice::{CSlice, AsCSlice}; use cslice::{CSlice, AsCSlice};
use board::csr;
use dyld::Library; use dyld::Library;
use proto::{kernel_proto, rpc_proto}; use proto::{kernel_proto, rpc_proto};
use proto::kernel_proto::*; use proto::kernel_proto::*;
@ -257,6 +258,8 @@ extern fn dma_record_start() {
} }
extern fn dma_record_stop(name: CSlice<u8>) { extern fn dma_record_stop(name: CSlice<u8>) {
let name = str::from_utf8(name.as_ref()).unwrap();
unsafe { unsafe {
if !DMA_RECORDING { if !DMA_RECORDING {
raise!("DMAError", "DMA is not recording") raise!("DMAError", "DMA is not recording")
@ -269,7 +272,7 @@ extern fn dma_record_stop(name: CSlice<u8>) {
rtio::output_wide as *const () as u32).unwrap(); rtio::output_wide as *const () as u32).unwrap();
DMA_RECORDING = false; DMA_RECORDING = false;
send(&DmaRecordStop(str::from_utf8(name.as_ref()).unwrap())); send(&DmaRecordStop(name));
} }
} }
@ -292,6 +295,64 @@ extern fn dma_record_output_wide(timestamp: i64, channel: i32, address: i32, dat
}) })
} }
extern fn dma_erase(name: CSlice<u8>) {
let name = str::from_utf8(name.as_ref()).unwrap();
send(&DmaEraseRequest(name));
}
extern fn dma_playback(timestamp: i64, name: CSlice<u8>) {
let name = str::from_utf8(name.as_ref()).unwrap();
send(&DmaPlaybackRequest(name));
let succeeded = recv!(&DmaPlaybackReply(data) => unsafe {
// Here, we take advantage of the fact that DmaPlaybackReply always refers
// to an entire heap allocation, which is 4-byte-aligned.
let data = match data { Some(bytes) => bytes, None => return false };
csr::rtio_dma::base_address_write(data.as_ptr() as u64);
csr::rtio_dma::time_offset_write(timestamp as u64);
csr::rtio_dma::enable_write(1);
while csr::rtio_dma::enable_read() != 0 {}
let status = csr::rtio_dma::error_status_read();
let timestamp = csr::rtio_dma::error_timestamp_read();
let channel = csr::rtio_dma::error_channel_read();
if status & rtio::RTIO_O_STATUS_UNDERFLOW != 0 {
csr::rtio_dma::error_underflow_reset_write(1);
raise!("RTIOUnderflow",
"RTIO underflow at {0} mu, channel {1}",
timestamp as i64, channel as i64, 0)
}
if status & rtio::RTIO_O_STATUS_SEQUENCE_ERROR != 0 {
csr::rtio_dma::error_sequence_error_reset_write(1);
raise!("RTIOSequenceError",
"RTIO sequence error at {0} mu, channel {1}",
timestamp as i64, channel as i64, 0)
}
if status & rtio::RTIO_O_STATUS_COLLISION != 0 {
csr::rtio_dma::error_collision_reset_write(1);
raise!("RTIOCollision",
"RTIO collision at {0} mu, channel {1}",
timestamp as i64, channel as i64, 0)
}
if status & rtio::RTIO_O_STATUS_BUSY != 0 {
csr::rtio_dma::error_busy_reset_write(1);
raise!("RTIOBusy",
"RTIO busy on channel {0}",
channel as i64, 0, 0)
}
true
});
if !succeeded {
println!("DMA trace called {:?} not found", name);
raise!("DMAError",
"DMA trace not found");
}
}
unsafe fn attribute_writeback(typeinfo: *const ()) { unsafe fn attribute_writeback(typeinfo: *const ()) {
struct Attr { struct Attr {
offset: usize, offset: usize,

View File

@ -4,13 +4,13 @@ use board::csr;
use ::send; use ::send;
use kernel_proto::*; use kernel_proto::*;
const RTIO_O_STATUS_FULL: u32 = 1; pub const RTIO_O_STATUS_FULL: u32 = 1;
const RTIO_O_STATUS_UNDERFLOW: u32 = 2; pub const RTIO_O_STATUS_UNDERFLOW: u32 = 2;
const RTIO_O_STATUS_SEQUENCE_ERROR: u32 = 4; pub const RTIO_O_STATUS_SEQUENCE_ERROR: u32 = 4;
const RTIO_O_STATUS_COLLISION: u32 = 8; pub const RTIO_O_STATUS_COLLISION: u32 = 8;
const RTIO_O_STATUS_BUSY: u32 = 16; pub const RTIO_O_STATUS_BUSY: u32 = 16;
const RTIO_I_STATUS_EMPTY: u32 = 1; pub const RTIO_I_STATUS_EMPTY: u32 = 1;
const RTIO_I_STATUS_OVERFLOW: u32 = 2; pub const RTIO_I_STATUS_OVERFLOW: u32 = 2;
pub extern fn init() { pub extern fn init() {
send(&RtioInitRequest); send(&RtioInitRequest);

View File

@ -38,6 +38,11 @@ pub enum Message<'a> {
}, },
DmaRecordStop(&'a str), DmaRecordStop(&'a str),
DmaEraseRequest(&'a str),
DmaPlaybackRequest(&'a str),
DmaPlaybackReply(Option<&'a [u8]>),
DrtioChannelStateRequest { channel: u32 }, DrtioChannelStateRequest { channel: u32 },
DrtioChannelStateReply { fifo_space: u16, last_timestamp: u64 }, DrtioChannelStateReply { fifo_space: u16, last_timestamp: u64 },
DrtioResetChannelStateRequest { channel: u32 }, DrtioResetChannelStateRequest { channel: u32 },

View File

@ -1,7 +1,8 @@
use std::mem;
use std::vec::Vec; use std::vec::Vec;
use std::string::String; use std::string::String;
use std::btree_map::BTreeMap; use std::btree_map::BTreeMap;
use std::mem; use std::io::Write;
use proto::WriteExt; use proto::WriteExt;
#[derive(Debug)] #[derive(Debug)]
@ -29,16 +30,26 @@ impl Manager {
pub fn record_append(&mut self, timestamp: u64, channel: u32, pub fn record_append(&mut self, timestamp: u64, channel: u32,
address: u32, data: &[u32]) { address: u32, data: &[u32]) {
let writer = &mut self.recording;
// See gateware/rtio/dma.py. // See gateware/rtio/dma.py.
let length = /*length*/1 + /*channel*/3 + /*timestamp*/8 + /*address*/2 + let length = /*length*/1 + /*channel*/3 + /*timestamp*/8 + /*address*/2 +
/*data*/data.len() * 4; /*data*/data.len() * 4;
let writer = &mut self.recording; writer.write_all(&[
writer.write_u8(length as u8).unwrap(); (length >> 0) as u8,
writer.write_u8((channel >> 24) as u8).unwrap(); (channel >> 0) as u8,
writer.write_u8((channel >> 16) as u8).unwrap(); (channel >> 8) as u8,
writer.write_u8((channel >> 8) as u8).unwrap(); (channel >> 16) as u8,
writer.write_u64(timestamp).unwrap(); (timestamp >> 0) as u8,
writer.write_u16(address as u16).unwrap(); (timestamp >> 8) as u8,
(timestamp >> 16) as u8,
(timestamp >> 24) as u8,
(timestamp >> 32) as u8,
(timestamp >> 40) as u8,
(timestamp >> 48) as u8,
(timestamp >> 56) as u8,
(address >> 0) as u8,
(address >> 8) as u8,
]).unwrap();
for &word in data { for &word in data {
writer.write_u32(word).unwrap(); writer.write_u32(word).unwrap();
} }
@ -55,4 +66,12 @@ impl Manager {
data: recorded data: recorded
}); });
} }
pub fn erase(&mut self, name: &str) {
self.entries.remove(name);
}
pub fn with_trace<F: FnOnce(Option<&[u8]>) -> R, R>(&self, name: &str, f: F) -> R {
f(self.entries.get(name).map(|entry| &entry.data[..]))
}
} }

View File

@ -408,6 +408,15 @@ fn process_kern_message(io: &Io, mut stream: Option<&mut TcpStream>,
session.congress.dma_manager.record_stop(name); session.congress.dma_manager.record_stop(name);
kern_acknowledge() kern_acknowledge()
} }
&kern::DmaEraseRequest(name) => {
session.congress.dma_manager.erase(name);
kern_acknowledge()
}
&kern::DmaPlaybackRequest(name) => {
session.congress.dma_manager.with_trace(name, |trace| {
kern_send(io, &kern::DmaPlaybackReply(trace))
})
}
&kern::DrtioChannelStateRequest { channel } => { &kern::DrtioChannelStateRequest { channel } => {
let (fifo_space, last_timestamp) = rtio_mgt::drtio_dbg::get_channel_state(channel); let (fifo_space, last_timestamp) = rtio_mgt::drtio_dbg::get_channel_state(channel);

View File

@ -457,3 +457,51 @@ class RPCTest(ExperimentCase):
self.assertGreater(rpc_time_mean, 100*ns) self.assertGreater(rpc_time_mean, 100*ns)
self.assertLess(rpc_time_mean, 2*ms) self.assertLess(rpc_time_mean, 2*ms)
self.assertLess(self.dataset_mgr.get("rpc_time_stddev"), 1*ms) self.assertLess(self.dataset_mgr.get("rpc_time_stddev"), 1*ms)
class _DMA(EnvExperiment):
def build(self, trace_name="foobar"):
self.setattr_device("core")
self.setattr_device("core_dma")
self.setattr_device("ttl0")
self.trace_name = trace_name
@kernel
def record(self):
with self.core_dma.record(self.trace_name):
delay(100*ns)
self.ttl0.on()
delay(100*ns)
self.ttl0.off()
@kernel
def replay(self):
self.core_dma.replay(self.trace_name)
@kernel
def erase(self):
self.core_dma.erase(self.trace_name)
@kernel
def nested(self):
with self.core_dma.record(self.trace_name):
with self.core_dma.record(self.trace_name):
pass
class DMATest(ExperimentCase):
def test_dma_storage(self):
exp = self.create(_DMA)
exp.record()
exp.record() # overwrite
exp.replay()
exp.erase()
with self.assertRaises(exceptions.DMAError):
exp.replay()
def test_dma_nested(self):
exp = self.create(_DMA)
with self.assertRaises(exceptions.DMAError):
exp.nested()
# TODO: check replay against analyzer