Merge pull request #441 from firesim/multiclock
Multiclock Staging Branch
This commit is contained in:
commit
3cf0d45e07
|
@ -327,7 +327,6 @@ class FireSimServerNode(FireSimNode):
|
|||
|
||||
all_paths.append([self.server_hardware_config.get_local_driver_path(), ''])
|
||||
all_paths.append([self.server_hardware_config.get_local_runtime_conf_path(), ''])
|
||||
all_paths.append([self.server_hardware_config.get_local_assert_def_path(), ''])
|
||||
|
||||
# shared libraries
|
||||
all_paths.append(["$RISCV/lib/libdwarf.so", "libdwarf.so.1"])
|
||||
|
@ -508,7 +507,6 @@ class FireSimSuperNodeServerNode(FireSimServerNode):
|
|||
|
||||
all_paths.append([self.server_hardware_config.get_local_driver_path(), ''])
|
||||
all_paths.append([self.server_hardware_config.get_local_runtime_conf_path(), ''])
|
||||
all_paths.append([self.server_hardware_config.get_local_assert_def_path(), ''])
|
||||
return all_paths
|
||||
|
||||
class FireSimDummyServerNode(FireSimServerNode):
|
||||
|
|
|
@ -79,14 +79,6 @@ class RuntimeHWConfig:
|
|||
runtime_conf_local = CUSTOM_RUNTIMECONFS_BASE + my_runtimeconfig
|
||||
return runtime_conf_local
|
||||
|
||||
# TODO: Delete this and bake the assertion definitions into the Driver
|
||||
def get_local_assert_def_path(self):
|
||||
""" return relative local path of the synthesized assertion definitions. """
|
||||
my_deploytriplet = self.get_deploytriplet_for_config()
|
||||
gen_src_dir = LOCAL_DRIVERS_GENERATED_SRC + "/" + my_deploytriplet + "/"
|
||||
assert_def_local = gen_src_dir + self.get_design_name() + ".asserts"
|
||||
return assert_def_local
|
||||
|
||||
def get_boot_simulation_command(self, slotid, all_macs,
|
||||
all_rootfses, all_linklatencies,
|
||||
all_netbws, profile_interval,
|
||||
|
@ -102,8 +94,8 @@ class RuntimeHWConfig:
|
|||
runtime parameters currently. """
|
||||
|
||||
# TODO: supernode support
|
||||
tracefile = "+tracefile0=TRACEFILE0" if trace_enable else ""
|
||||
autocounterfile = "+autocounter-filename0=AUTOCOUNTERFILE0"
|
||||
tracefile = "+tracefile=TRACEFILE" if trace_enable else ""
|
||||
autocounterfile = "+autocounter-filename=AUTOCOUNTERFILE"
|
||||
|
||||
# this monstrosity boots the simulator, inside screen, inside script
|
||||
# the sed is in there to get rid of newlines in runtime confs
|
||||
|
@ -135,10 +127,10 @@ class RuntimeHWConfig:
|
|||
zero_out_dram = "+zero-out-dram" if (enable_zerooutdram) else ""
|
||||
|
||||
# TODO supernode support
|
||||
dwarf_file_name = "+dwarf-file-name0=" + all_bootbinaries[0] + "-dwarf"
|
||||
dwarf_file_name = "+dwarf-file-name=" + all_bootbinaries[0] + "-dwarf"
|
||||
|
||||
# TODO: supernode support (tracefile0, trace-select0.. etc)
|
||||
basecommand = """screen -S fsim{slotid} -d -m bash -c "script -f -c 'stty intr ^] && sudo sudo LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./{driver} +permissive $(sed \':a;N;$!ba;s/\\n/ /g\' {runtimeconf}) +slotid={slotid} +profile-interval={profile_interval} {zero_out_dram} {command_macs} {command_rootfses} {command_niclogs} {command_blkdev_logs} {tracefile} +trace-select0={trace_select} +trace-start0={trace_start} +trace-end0={trace_end} +trace-output-format0={trace_output_format} {dwarf_file_name} +autocounter-readrate0={autocounter_readrate} {autocounterfile} {command_linklatencies} {command_netbws} {command_shmemportnames} +permissive-off {command_bootbinaries} && stty intr ^c' uartlog"; sleep 1""".format(
|
||||
# TODO: supernode support (tracefile, trace-select.. etc)
|
||||
basecommand = """screen -S fsim{slotid} -d -m bash -c "script -f -c 'stty intr ^] && sudo sudo LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./{driver} +permissive $(sed \':a;N;$!ba;s/\\n/ /g\' {runtimeconf}) +slotid={slotid} +profile-interval={profile_interval} {zero_out_dram} {command_macs} {command_rootfses} {command_niclogs} {command_blkdev_logs} {tracefile} +trace-select={trace_select} +trace-start={trace_start} +trace-end={trace_end} +trace-output-format={trace_output_format} {dwarf_file_name} +autocounter-readrate={autocounter_readrate} {autocounterfile} {command_linklatencies} {command_netbws} {command_shmemportnames} +permissive-off {command_bootbinaries} && stty intr ^c' uartlog"; sleep 1""".format(
|
||||
slotid=slotid, driver=driver, runtimeconf=runtimeconf,
|
||||
command_macs=command_macs,
|
||||
command_rootfses=command_rootfses,
|
||||
|
|
|
@ -370,9 +370,9 @@ class UserTopologies(object):
|
|||
# Spins up all of the precompiled, unnetworked targets
|
||||
def all_no_net_targets_config(self):
|
||||
hwdb_entries = [
|
||||
"fireboom-singlecore-no-nic-l2-llc4mb-ddr3",
|
||||
"fireboom-singlecore-no-nic-l2-llc4mb-ddr3-ramopts",
|
||||
"firesim-quadcore-no-nic-l2-llc4mb-ddr3",
|
||||
"firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3",
|
||||
"firesim-rocket-quadcore-no-nic-l2-llc4mb-ddr3",
|
||||
"firesim-rocket-quadcore-no-nic-l2-llc4mb-ddr3-halfrate",
|
||||
]
|
||||
assert len(hwdb_entries) == self.no_net_num_nodes
|
||||
self.roots = [FireSimServerNode(hwdb_entries[x]) for x in range(self.no_net_num_nodes)]
|
||||
|
|
|
@ -18,7 +18,8 @@ firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3
|
|||
firesim-boom-singlecore-nic-l2-llc4mb-ddr3
|
||||
|
||||
firesim-supernode-rocket-singlecore-nic-l2-lbp
|
||||
firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3-ramopts
|
||||
#firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3-ramopts
|
||||
firesim-rocket-quadcore-no-nic-l2-llc4mb-ddr3-half-freq-uncore
|
||||
|
||||
firesim-rocket-singlecore-gemmini-no-nic-l2-llc4mb-ddr3
|
||||
|
||||
|
@ -33,7 +34,8 @@ firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3
|
|||
firesim-boom-singlecore-nic-l2-llc4mb-ddr3
|
||||
|
||||
firesim-supernode-rocket-singlecore-nic-l2-lbp
|
||||
firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3-ramopts
|
||||
#firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3-ramopts
|
||||
firesim-rocket-quadcore-no-nic-l2-llc4mb-ddr3-half-freq-uncore
|
||||
|
||||
firesim-rocket-singlecore-gemmini-no-nic-l2-llc4mb-ddr3
|
||||
|
||||
|
|
|
@ -77,6 +77,13 @@ PLATFORM_CONFIG=F85MHz_BaseF1Config
|
|||
instancetype=z1d.2xlarge
|
||||
deploytriplet=None
|
||||
|
||||
# Multiclock Temporary Example:ncept Quad-core, Rocket-based recipes
|
||||
[firesim-rocket-quadcore-no-nic-l2-llc4mb-ddr3-half-freq-uncore]
|
||||
DESIGN=FireSimMulticlockPOC
|
||||
TARGET_CONFIG=DDR3FRFCFSLLC4MB_FireSimQuadRocketMulticlockConfig
|
||||
PLATFORM_CONFIG=F90MHz_BaseF1Config
|
||||
instancetype=z1d.2xlarge
|
||||
deploytriplet=None
|
||||
|
||||
# MIDAS Examples -- BUILD SUPPORT ONLY; Can't launch driver correctly on runfarm
|
||||
[midasexamples-gcd]
|
||||
|
|
|
@ -8,38 +8,39 @@
|
|||
# Only AGFIs for the latest release of FireSim are guaranteed to be available.
|
||||
# If you are using an older version of FireSim, you will need to generate your
|
||||
# own images.
|
||||
|
||||
[firesim-boom-singlecore-nic-l2-llc4mb-ddr3]
|
||||
agfi=agfi-0d80edc1e51eeed2a
|
||||
agfi=agfi-0b00913f35fd3b17b
|
||||
deploytripletoverride=None
|
||||
customruntimeconfig=None
|
||||
|
||||
[firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3]
|
||||
agfi=agfi-062b20613c52a2313
|
||||
deploytripletoverride=None
|
||||
customruntimeconfig=None
|
||||
|
||||
[firesim-boom-singlecore-no-nic-l2-llc4mb-ddr3-ramopts]
|
||||
agfi=agfi-0cfb3cb54b7928d08
|
||||
agfi=agfi-06addb09b5f339914
|
||||
deploytripletoverride=None
|
||||
customruntimeconfig=None
|
||||
|
||||
[firesim-rocket-quadcore-nic-l2-llc4mb-ddr3]
|
||||
agfi=agfi-0c98ad02959d4fd95
|
||||
agfi=agfi-0fb5684b14d71b79f
|
||||
deploytripletoverride=None
|
||||
customruntimeconfig=None
|
||||
|
||||
[firesim-rocket-quadcore-no-nic-l2-llc4mb-ddr3]
|
||||
agfi=agfi-04812c9eb510b7913
|
||||
agfi=agfi-05fb794ef6d3fa423
|
||||
deploytripletoverride=None
|
||||
customruntimeconfig=None
|
||||
|
||||
[firesim-rocket-quadcore-no-nic-l2-llc4mb-ddr3-half-freq-uncore]
|
||||
agfi=agfi-0cde94735d54677e7
|
||||
deploytripletoverride=None
|
||||
customruntimeconfig=None
|
||||
|
||||
[firesim-rocket-singlecore-gemmini-no-nic-l2-llc4mb-ddr3]
|
||||
agfi=agfi-006f97febc774fee1
|
||||
agfi=agfi-047da9dd6850b8e9d
|
||||
deploytripletoverride=None
|
||||
customruntimeconfig=None
|
||||
|
||||
[firesim-supernode-rocket-singlecore-nic-l2-lbp]
|
||||
agfi=agfi-05617d1eb6490d689
|
||||
agfi=agfi-0f0efddd34003118d
|
||||
deploytripletoverride=None
|
||||
customruntimeconfig=None
|
||||
|
||||
|
|
|
@ -14,19 +14,6 @@
|
|||
|
||||
#include <sys/mman.h>
|
||||
|
||||
// TODO: generate a header with these automatically
|
||||
|
||||
// bitwidths for stuff in the trace. assume this order too.
|
||||
#define VALID_WID 1
|
||||
#define IADDR_WID 40
|
||||
#define INSN_WID 32
|
||||
#define PRIV_WID 3
|
||||
#define EXCP_WID 1
|
||||
#define INT_WID 1
|
||||
#define CAUSE_WID 8
|
||||
#define TVAL_WID 40
|
||||
#define TOTAL_WID (VALID_WID + IADDR_WID + INSN_WID + PRIV_WID + EXCP_WID + INT_WID + CAUSE_WID + TVAL_WID)
|
||||
|
||||
// The maximum number of beats available in the FPGA-side FIFO
|
||||
#define QUEUE_DEPTH 6144
|
||||
|
||||
|
@ -34,18 +21,28 @@
|
|||
// useful for iterating on software side only without re-running on FPGA.
|
||||
//#define FIREPERF_LOGGER
|
||||
|
||||
constexpr uint64_t valid_mask = (1ULL << 40);
|
||||
|
||||
tracerv_t::tracerv_t(
|
||||
simif_t *sim, std::vector<std::string> &args, TRACERVBRIDGEMODULE_struct * mmio_addrs, int tracerno, long dma_addr) : bridge_driver_t(sim)
|
||||
{
|
||||
static_assert(NUM_CORES <= 7, "TRACERV CURRENTLY ONLY SUPPORT <= 7 Cores/Instruction Streams");
|
||||
this->mmio_addrs = mmio_addrs;
|
||||
this->dma_addr = dma_addr;
|
||||
simif_t *sim,
|
||||
std::vector<std::string> &args,
|
||||
TRACERVBRIDGEMODULE_struct * mmio_addrs,
|
||||
long dma_addr,
|
||||
const unsigned int max_core_ipc,
|
||||
const char* const clock_domain_name,
|
||||
const unsigned int clock_multiplier,
|
||||
const unsigned int clock_divisor,
|
||||
int tracerno) :
|
||||
bridge_driver_t(sim),
|
||||
mmio_addrs(mmio_addrs),
|
||||
max_core_ipc(max_core_ipc),
|
||||
clock_info(clock_domain_name, clock_multiplier, clock_divisor),
|
||||
dma_addr(dma_addr) {
|
||||
//Biancolin: move into elaboration
|
||||
assert(this->max_core_ipc <= 7 && "TracerV only supports cores with a maximum IPC <= 7");
|
||||
const char *tracefilename = NULL;
|
||||
const char *dwarf_file_name = NULL;
|
||||
|
||||
for (int i = 0; i < NUM_CORES; i++) {
|
||||
this->tracefiles[i] = NULL;
|
||||
}
|
||||
this->tracefile = NULL;
|
||||
|
||||
this->trace_trigger_start = 0;
|
||||
this->trace_trigger_end = ULONG_MAX;
|
||||
|
@ -55,18 +52,18 @@ tracerv_t::tracerv_t(
|
|||
|
||||
long outputfmtselect = 0;
|
||||
|
||||
std::string num_equals = std::to_string(tracerno) + std::string("=");
|
||||
std::string tracefile_arg = std::string("+tracefile") + num_equals;
|
||||
std::string tracestart_arg = std::string("+trace-start") + num_equals;
|
||||
std::string traceend_arg = std::string("+trace-end") + num_equals;
|
||||
std::string traceselect_arg = std::string("+trace-select") + num_equals;
|
||||
std::string suffix = std::string("=");
|
||||
std::string tracefile_arg = std::string("+tracefile") + suffix;
|
||||
std::string tracestart_arg = std::string("+trace-start") + suffix;
|
||||
std::string traceend_arg = std::string("+trace-end") + suffix;
|
||||
std::string traceselect_arg = std::string("+trace-select") + suffix;
|
||||
// Testing: provides a reference file to diff the collected trace against
|
||||
std::string testoutput_arg = std::string("+trace-test-output") + std::to_string(tracerno);
|
||||
std::string testoutput_arg = std::string("+trace-test-output");
|
||||
// Formats the output before dumping the trace to file
|
||||
std::string humanreadable_arg = std::string("+trace-humanreadable") + std::to_string(tracerno);
|
||||
std::string humanreadable_arg = std::string("+trace-humanreadable");
|
||||
|
||||
std::string trace_output_format_arg = std::string("+trace-output-format") + num_equals;
|
||||
std::string dwarf_file_arg = std::string("+dwarf-file-name") + num_equals;
|
||||
std::string trace_output_format_arg = std::string("+trace-output-format") + suffix;
|
||||
std::string dwarf_file_arg = std::string("+dwarf-file-name") + suffix;
|
||||
|
||||
for (auto &arg: args) {
|
||||
if (arg.find(tracefile_arg) == 0) {
|
||||
|
@ -77,15 +74,26 @@ tracerv_t::tracerv_t(
|
|||
char *str = const_cast<char*>(arg.c_str()) + traceselect_arg.length();
|
||||
this->trigger_selector = atol(str);
|
||||
}
|
||||
// These next two arguments are overloaded to provide trigger start and
|
||||
// stop condition information based on setting of the +trace-select
|
||||
if (arg.find(tracestart_arg) == 0) {
|
||||
// Start and end cycles are given in decimal
|
||||
char *str = const_cast<char*>(arg.c_str()) + tracestart_arg.length();
|
||||
char * pEnd;
|
||||
this->trace_trigger_start = trigger_selector==1 ? atol(str) : strtoul (str,&pEnd,16);
|
||||
this->trace_trigger_start = this->clock_info.to_local_cycles(atol(str));
|
||||
// PCs values, and instruction and mask encodings are given in hex
|
||||
uint64_t mask_and_insn = strtoul(str, NULL, 16);
|
||||
this->trigger_start_insn = (uint32_t) mask_and_insn;
|
||||
this->trigger_start_insn_mask = mask_and_insn >> 32;
|
||||
this->trigger_start_pc = mask_and_insn;
|
||||
}
|
||||
if (arg.find(traceend_arg) == 0) {
|
||||
char *str = const_cast<char*>(arg.c_str()) + traceend_arg.length();
|
||||
char * pEnd;
|
||||
this->trace_trigger_end = trigger_selector==1 ? atol(str) : strtoul (str,&pEnd,16);
|
||||
this->trace_trigger_end = this->clock_info.to_local_cycles(atol(str));
|
||||
|
||||
uint64_t mask_and_insn = strtoul(str, NULL, 16);
|
||||
this->trigger_stop_insn = (uint32_t) mask_and_insn;
|
||||
this->trigger_stop_insn_mask = mask_and_insn >> 32;
|
||||
this->trigger_stop_pc = mask_and_insn;
|
||||
}
|
||||
if (arg.find(testoutput_arg) == 0) {
|
||||
this->test_output = true;
|
||||
|
@ -102,14 +110,13 @@ tracerv_t::tracerv_t(
|
|||
|
||||
if (tracefilename) {
|
||||
// giving no tracefilename means we will create NO tracefiles
|
||||
for (int i = 0; i < NUM_CORES; i++) {
|
||||
std::string tfname = std::string(tracefilename) + std::string("-C") + std::to_string(i);
|
||||
this->tracefiles[i] = fopen(tfname.c_str(), "w");
|
||||
if (!this->tracefiles[i]) {
|
||||
fprintf(stderr, "Could not open Trace log file: %s\n", tracefilename);
|
||||
abort();
|
||||
}
|
||||
std::string tfname = std::string(tracefilename) + std::string("-C") + std::to_string(tracerno);
|
||||
this->tracefile = fopen(tfname.c_str(), "w");
|
||||
if (!this->tracefile) {
|
||||
fprintf(stderr, "Could not open Trace log file: %s\n", tracefilename);
|
||||
abort();
|
||||
}
|
||||
fputs(this->clock_info.file_header().c_str(), this->tracefile);
|
||||
|
||||
// This must be kept consistent with config_runtime.ini's output_format.
|
||||
// That file's comments are the single source of truth for this.
|
||||
|
@ -126,7 +133,8 @@ tracerv_t::tracerv_t(
|
|||
fprintf(stderr, "Invalid trace format arg\n");
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "TraceRV: Warning: No +tracefileN given!\n");
|
||||
fprintf(stderr, "TraceRV %d: Tracing disabled, since +tracefile was not provided.\n", tracerno);
|
||||
this->trace_enabled = false;
|
||||
}
|
||||
|
||||
if (fireperf) {
|
||||
|
@ -134,23 +142,28 @@ tracerv_t::tracerv_t(
|
|||
fprintf(stderr, "+fireperf specified but no +dwarf-file-name given\n");
|
||||
abort();
|
||||
}
|
||||
for (int i = 0; i < NUM_CORES; i++) {
|
||||
this->trace_trackers[i] = new TraceTracker(this->dwarf_file_name, this->tracefiles[i]);
|
||||
}
|
||||
this->trace_tracker = new TraceTracker(this->dwarf_file_name, this->tracefile);
|
||||
}
|
||||
}
|
||||
|
||||
tracerv_t::~tracerv_t() {
|
||||
for (int i = 0; i < NUM_CORES; i++) {
|
||||
if (this->tracefiles[i]) {
|
||||
fclose(this->tracefiles[i]);
|
||||
}
|
||||
if (this->tracefile) {
|
||||
fclose(this->tracefile);
|
||||
}
|
||||
free(this->mmio_addrs);
|
||||
}
|
||||
|
||||
void tracerv_t::init() {
|
||||
if (this->trigger_selector == 1)
|
||||
if (!this->trace_enabled) {
|
||||
// Explicitly disable token collection inthe bridge by only collecting
|
||||
// tokens from cycle 0 to cycle 0, saving DMA bandwidth and improving FMR
|
||||
write(this->mmio_addrs->triggerSelector, 1);
|
||||
write(this->mmio_addrs->hostTriggerCycleCountStartHigh, 0);
|
||||
write(this->mmio_addrs->hostTriggerCycleCountStartLow, 0);
|
||||
write(this->mmio_addrs->hostTriggerCycleCountEndHigh, 0);
|
||||
write(this->mmio_addrs->hostTriggerCycleCountEndLow, 0);
|
||||
}
|
||||
else if (this->trigger_selector == 1)
|
||||
{
|
||||
write(this->mmio_addrs->triggerSelector, this->trigger_selector);
|
||||
write(this->mmio_addrs->hostTriggerCycleCountStartHigh, this->trace_trigger_start >> 32);
|
||||
|
@ -162,93 +175,97 @@ void tracerv_t::init() {
|
|||
else if (this->trigger_selector == 2)
|
||||
{
|
||||
write(this->mmio_addrs->triggerSelector, this->trigger_selector);
|
||||
write(this->mmio_addrs->hostTriggerPCStartHigh, this->trace_trigger_start >> 32);
|
||||
write(this->mmio_addrs->hostTriggerPCStartLow, this->trace_trigger_start & ((1ULL << 32) - 1));
|
||||
write(this->mmio_addrs->hostTriggerPCEndHigh, this->trace_trigger_end >> 32);
|
||||
write(this->mmio_addrs->hostTriggerPCEndLow, this->trace_trigger_end & ((1ULL << 32) - 1));
|
||||
printf("TracerV: Collect trace from instruction address %lx to %lx\n", trace_trigger_start, trace_trigger_end);
|
||||
write(this->mmio_addrs->hostTriggerPCStartHigh, this->trigger_start_pc >> 32);
|
||||
write(this->mmio_addrs->hostTriggerPCStartLow, this->trigger_start_pc & ((1ULL << 32) - 1));
|
||||
write(this->mmio_addrs->hostTriggerPCEndHigh, this->trigger_stop_pc >> 32);
|
||||
write(this->mmio_addrs->hostTriggerPCEndLow, this->trigger_stop_pc & ((1ULL << 32) - 1));
|
||||
printf("TracerV: Collect trace from instruction address %lx to %lx\n", trigger_start_pc, trigger_stop_pc);
|
||||
}
|
||||
else if (this->trigger_selector == 3)
|
||||
{
|
||||
write(this->mmio_addrs->triggerSelector, this->trigger_selector);
|
||||
write(this->mmio_addrs->hostTriggerStartInst, this->trace_trigger_start & ((1ULL << 32) - 1));
|
||||
write(this->mmio_addrs->hostTriggerStartInstMask, this->trace_trigger_start >> 32);
|
||||
write(this->mmio_addrs->hostTriggerEndInst, this->trace_trigger_end & ((1ULL << 32) - 1));
|
||||
write(this->mmio_addrs->hostTriggerEndInstMask, this->trace_trigger_end >> 32);
|
||||
write(this->mmio_addrs->hostTriggerStartInst, this->trigger_start_insn);
|
||||
write(this->mmio_addrs->hostTriggerStartInstMask, this->trigger_start_insn_mask);
|
||||
write(this->mmio_addrs->hostTriggerEndInst, this->trigger_stop_insn);
|
||||
write(this->mmio_addrs->hostTriggerEndInstMask, this->trigger_stop_insn_mask);
|
||||
printf("TracerV: Collect trace with start trigger instruction %x masked with %x, and end trigger instruction %x masked with %x\n",
|
||||
this->trace_trigger_start & ((1ULL << 32) - 1), this->trace_trigger_start >> 32,
|
||||
this->trace_trigger_end & ((1ULL << 32) - 1), this->trace_trigger_end >> 32);
|
||||
this->trigger_start_insn, this->trigger_start_insn_mask,
|
||||
this->trigger_stop_insn, this->trigger_stop_insn_mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Biancolin: should we not error here?
|
||||
write(this->mmio_addrs->triggerSelector, this->trigger_selector);
|
||||
printf("TracerV: Collecting trace from %lu to %lu cycles\n", trace_trigger_start, trace_trigger_end);
|
||||
printf("TracerV: No trigger selected. Collecting trace from %lu to %lu cycles\n", 0, ULONG_MAX);
|
||||
}
|
||||
write(this->mmio_addrs->initDone, true);
|
||||
}
|
||||
|
||||
// defining this stores as human readable hex (e.g. open in VIM)
|
||||
// undefining this stores as bin (e.g. open with vim hex mode)
|
||||
|
||||
void tracerv_t::tick() {
|
||||
uint64_t outfull = read(this->mmio_addrs->tracequeuefull);
|
||||
|
||||
void tracerv_t::process_tokens(int num_beats) {
|
||||
// TODO. as opt can mmap file and just load directly into it.
|
||||
alignas(4096) uint64_t OUTBUF[QUEUE_DEPTH * 8];
|
||||
|
||||
if (outfull) {
|
||||
// TODO. as opt can mmap file and just load directly into it.
|
||||
pull(dma_addr, (char*)OUTBUF, QUEUE_DEPTH * 64);
|
||||
//check that a tracefile exists (one is enough) since the manager
|
||||
//does not create a tracefile when trace_enable is disabled, but the
|
||||
//TracerV bridge still exists, and no tracefiles are create be default.
|
||||
if (this->tracefiles[0]) {
|
||||
if (this->human_readable || this->test_output) {
|
||||
for (int i = 0; i < QUEUE_DEPTH * 8; i+=8) {
|
||||
if (this->test_output) {
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+7]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+6]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+5]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+4]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+3]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+2]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+1]);
|
||||
fprintf(this->tracefiles[0], "%016lx\n", OUTBUF[i+0]);
|
||||
} else {
|
||||
for (int q = 0; q < NUM_CORES; q++) {
|
||||
if ((OUTBUF[i+0+q] >> 40) & 0x1) {
|
||||
fprintf(this->tracefiles[q], "C%d: %016llx, cycle: %016llx\n", q, OUTBUF[i+0+q], OUTBUF[i+7]);
|
||||
}
|
||||
}
|
||||
pull(dma_addr, (char*)OUTBUF, num_beats * 64);
|
||||
//check that a tracefile exists (one is enough) since the manager
|
||||
//does not create a tracefile when trace_enable is disabled, but the
|
||||
//TracerV bridge still exists, and no tracefile is created by default.
|
||||
if (this->tracefile) {
|
||||
if (this->human_readable || this->test_output) {
|
||||
for (int i = 0; i < num_beats * 8; i+=8) {
|
||||
if (this->test_output) {
|
||||
fprintf(this->tracefile, "%016lx", OUTBUF[i+7]);
|
||||
fprintf(this->tracefile, "%016lx", OUTBUF[i+6]);
|
||||
fprintf(this->tracefile, "%016lx", OUTBUF[i+5]);
|
||||
fprintf(this->tracefile, "%016lx", OUTBUF[i+4]);
|
||||
fprintf(this->tracefile, "%016lx", OUTBUF[i+3]);
|
||||
fprintf(this->tracefile, "%016lx", OUTBUF[i+2]);
|
||||
fprintf(this->tracefile, "%016lx", OUTBUF[i+1]);
|
||||
fprintf(this->tracefile, "%016lx\n", OUTBUF[i+0]);
|
||||
// At least one valid instruction
|
||||
} else {
|
||||
for (int q = 0; q < max_core_ipc; q++) {
|
||||
if (OUTBUF[i+q+1] & valid_mask) {
|
||||
fprintf(this->tracefile, "Cycle: %016lld I%d: %016llx\n", OUTBUF[i+0], q, OUTBUF[i+q+1] & (~valid_mask));
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (this->fireperf) {
|
||||
for (int i = 0; i < QUEUE_DEPTH * 8; i+=8) {
|
||||
uint64_t cycle_internal = OUTBUF[i+7];
|
||||
}
|
||||
} else if (this->fireperf) {
|
||||
|
||||
for (int q = 0; q < NUM_CORES; q++) {
|
||||
if ((OUTBUF[i+0+q] >> 40) & 0x1) {
|
||||
uint64_t iaddr = (uint64_t)((((int64_t)(OUTBUF[i+0+q])) << 24) >> 24);
|
||||
this->trace_trackers[q]->addInstruction(iaddr, cycle_internal);
|
||||
for (int i = 0; i < QUEUE_DEPTH * 8; i+=8) {
|
||||
uint64_t cycle_internal = OUTBUF[i+0];
|
||||
|
||||
for (int q = 0; q < max_core_ipc; q++) {
|
||||
if (OUTBUF[i+1+q] & valid_mask) {
|
||||
uint64_t iaddr = (uint64_t)((((int64_t)(OUTBUF[i+1+q])) << 24) >> 24);
|
||||
this->trace_tracker->addInstruction(iaddr, cycle_internal);
|
||||
#ifdef FIREPERF_LOGGER
|
||||
fprintf(this->tracefiles[q], "%016llx", iaddr);
|
||||
fprintf(this->tracefiles[q], "%016llx\n", cycle_internal);
|
||||
fprintf(this->tracefile, "%016llx", iaddr);
|
||||
fprintf(this->tracefile, "%016llx\n", cycle_internal);
|
||||
#endif //FIREPERF_LOGGER
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < QUEUE_DEPTH * 8; i+=8) {
|
||||
// this stores as raw binary. stored as little endian.
|
||||
// e.g. to get the same thing as the human readable above,
|
||||
// flip all the bytes in each 512-bit line.
|
||||
for (int q = 0; q < 8; q++) {
|
||||
fwrite(OUTBUF + (i+q), sizeof(uint64_t), 1, this->tracefiles[0]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < QUEUE_DEPTH * 8; i+=8) {
|
||||
// this stores as raw binary. stored as little endian.
|
||||
// e.g. to get the same thing as the human readable above,
|
||||
// flip all the bytes in each 512-bit line.
|
||||
for (int q = 0; q < 8; q++) {
|
||||
fwrite(OUTBUF + (i+q), sizeof(uint64_t), 1, this->tracefile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tracerv_t::tick() {
|
||||
if (this->trace_enabled) {
|
||||
uint64_t outfull = read(this->mmio_addrs->tracequeuefull);
|
||||
if (outfull) process_tokens(QUEUE_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
int tracerv_t::beats_available_stable() {
|
||||
size_t prev_beats_available = 0;
|
||||
|
@ -264,64 +281,9 @@ int tracerv_t::beats_available_stable() {
|
|||
// Pull in any remaining tokens and flush them to file
|
||||
// WARNING: may not function correctly if the simulator is actively running
|
||||
void tracerv_t::flush() {
|
||||
|
||||
alignas(4096) uint64_t OUTBUF[QUEUE_DEPTH * 8];
|
||||
size_t beats_available = beats_available_stable();
|
||||
|
||||
// TODO. as opt can mmap file and just load directly into it.
|
||||
pull(dma_addr, (char*)OUTBUF, beats_available * 64);
|
||||
//check that a tracefile exists (one is enough) since the manager
|
||||
//does not create a tracefile when trace_enable is disabled, but the
|
||||
//TracerV bridge still exists, and no tracefiles are create be default.
|
||||
if (this->tracefiles[0]) {
|
||||
if (this->human_readable || this->test_output) {
|
||||
for (int i = 0; i < beats_available * 8; i+=8) {
|
||||
|
||||
if (this->test_output) {
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+7]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+6]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+5]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+4]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+3]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+2]);
|
||||
fprintf(this->tracefiles[0], "%016lx", OUTBUF[i+1]);
|
||||
fprintf(this->tracefiles[0], "%016lx\n", OUTBUF[i+0]);
|
||||
} else {
|
||||
for (int q = 0; q < NUM_CORES; q++) {
|
||||
if ((OUTBUF[i+0+q] >> 40) & 0x1) {
|
||||
fprintf(this->tracefiles[q], "C%d: %016llx, cycle: %016llx\n", q, OUTBUF[i+0+q], OUTBUF[i+7]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (this->fireperf) {
|
||||
for (int i = 0; i < beats_available * 8; i+=8) {
|
||||
uint64_t cycle_internal = OUTBUF[i+7];
|
||||
|
||||
for (int q = 0; q < NUM_CORES; q++) {
|
||||
if ((OUTBUF[i+0+q] >> 40) & 0x1) {
|
||||
// is a valid instruction
|
||||
//
|
||||
// sign extended from sv39
|
||||
uint64_t iaddr = (uint64_t)((((int64_t)(OUTBUF[i+0+q])) << 24) >> 24);
|
||||
this->trace_trackers[q]->addInstruction(iaddr, cycle_internal);
|
||||
#ifdef FIREPERF_LOGGER
|
||||
fprintf(this->tracefiles[q], "%016llx", iaddr);
|
||||
fprintf(this->tracefiles[q], "%016llx\n", cycle_internal);
|
||||
#endif //FIREPERF_LOGGER
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < beats_available * 8; i+=8) {
|
||||
// this stores as raw binary. stored as little endian.
|
||||
// e.g. to get the same thing as the human readable above,
|
||||
// flip all the bytes in each 512-bit line.
|
||||
for (int q = 0; q < 8; q++) {
|
||||
fwrite(OUTBUF + (i+q), sizeof(uint64_t), 1, this->tracefiles[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this->trace_enabled) {
|
||||
size_t beats_available = beats_available_stable();
|
||||
process_tokens(beats_available);
|
||||
}
|
||||
}
|
||||
#endif // TRACERVBRIDGEMODULE_struct_guard
|
||||
|
|
|
@ -3,20 +3,40 @@
|
|||
#define __TRACERV_H
|
||||
|
||||
#include "bridges/bridge_driver.h"
|
||||
#include "bridges/clock_info.h"
|
||||
#include <vector>
|
||||
#include "bridges/tracerv/tracerv_processing.h"
|
||||
#include "bridges/tracerv/trace_tracker.h"
|
||||
|
||||
// TODO: get this automatically
|
||||
#define NUM_CORES 1
|
||||
|
||||
#ifdef TRACERVBRIDGEMODULE_struct_guard
|
||||
|
||||
// Bridge Driver Instantiation Template
|
||||
#define INSTANTIATE_TRACERV(FUNC,IDX) \
|
||||
TRACERVBRIDGEMODULE_ ## IDX ## _substruct_create; \
|
||||
FUNC(new tracerv_t( \
|
||||
this, \
|
||||
args, \
|
||||
TRACERVBRIDGEMODULE_ ## IDX ## _substruct, \
|
||||
TRACERVBRIDGEMODULE_ ## IDX ## _DMA_ADDR, \
|
||||
TRACERVBRIDGEMODULE_ ## IDX ## _max_core_ipc, \
|
||||
TRACERVBRIDGEMODULE_ ## IDX ## _clock_domain_name, \
|
||||
TRACERVBRIDGEMODULE_ ## IDX ## _clock_multiplier, \
|
||||
TRACERVBRIDGEMODULE_ ## IDX ## _clock_divisor, \
|
||||
IDX)); \
|
||||
|
||||
class tracerv_t: public bridge_driver_t
|
||||
|
||||
{
|
||||
public:
|
||||
tracerv_t(simif_t *sim, std::vector<std::string> &args,
|
||||
TRACERVBRIDGEMODULE_struct * mmio_addrs, int tracervno, long dma_addr);
|
||||
tracerv_t(simif_t *sim,
|
||||
std::vector<std::string> &args,
|
||||
TRACERVBRIDGEMODULE_struct * mmio_addrs,
|
||||
long dma_addr,
|
||||
const unsigned int max_core_ipc,
|
||||
const char* const clock_domain_name,
|
||||
const unsigned int clock_multiplier,
|
||||
const unsigned int clock_divisor,
|
||||
int tracerno);
|
||||
~tracerv_t();
|
||||
|
||||
virtual void init();
|
||||
|
@ -27,25 +47,38 @@ class tracerv_t: public bridge_driver_t
|
|||
|
||||
private:
|
||||
TRACERVBRIDGEMODULE_struct * mmio_addrs;
|
||||
simif_t* sim;
|
||||
FILE * tracefiles[NUM_CORES];
|
||||
const int max_core_ipc;
|
||||
ClockInfo clock_info;
|
||||
|
||||
FILE * tracefile;
|
||||
uint64_t cur_cycle;
|
||||
uint64_t trace_trigger_start, trace_trigger_end;
|
||||
uint32_t trigger_start_insn = 0;
|
||||
uint32_t trigger_start_insn_mask = 0;
|
||||
uint32_t trigger_stop_insn = 0;
|
||||
uint32_t trigger_stop_insn_mask = 0;
|
||||
uint32_t trigger_selector;
|
||||
uint64_t trigger_start_pc = 0;
|
||||
uint64_t trigger_stop_pc = 0;
|
||||
|
||||
// TODO: rename this from linuxbin
|
||||
ObjdumpedBinary * linuxbin;
|
||||
TraceTracker * trace_trackers[NUM_CORES];
|
||||
TraceTracker * trace_tracker;
|
||||
|
||||
bool human_readable = false;
|
||||
// If no filename is provided, the instruction trace is not collected
|
||||
// and the bridge drops all tokens to improve FMR
|
||||
bool trace_enabled = true;
|
||||
// Used in unit testing to check TracerV is correctly pulling instuctions off the target
|
||||
bool test_output = false;
|
||||
long dma_addr;
|
||||
void flush();
|
||||
int beats_available_stable();
|
||||
std::string tracefilename;
|
||||
std::string dwarf_file_name;
|
||||
bool fireperf = false;
|
||||
|
||||
void process_tokens(int num_beats);
|
||||
int beats_available_stable();
|
||||
void flush();
|
||||
};
|
||||
#endif // TRACERVBRIDGEMODULE_struct_guard
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import testchipip.{BlockDeviceIO, BlockDeviceRequest, BlockDeviceData, BlockDevi
|
|||
class BlockDevBridgeTargetIO(implicit val p: Parameters) extends Bundle {
|
||||
val bdev = Flipped(new BlockDeviceIO)
|
||||
val reset = Input(Bool())
|
||||
val clock = Input(Clock())
|
||||
}
|
||||
|
||||
class BlockDevBridge(implicit p: Parameters) extends BlackBox
|
||||
|
@ -25,9 +26,10 @@ class BlockDevBridge(implicit p: Parameters) extends BlackBox
|
|||
}
|
||||
|
||||
object BlockDevBridge {
|
||||
def apply(blkdevIO: BlockDeviceIO, reset: Bool)(implicit p: Parameters): BlockDevBridge = {
|
||||
def apply(clock: Clock, blkdevIO: BlockDeviceIO, reset: Bool)(implicit p: Parameters): BlockDevBridge = {
|
||||
val ep = Module(new BlockDevBridge)
|
||||
ep.io.bdev <> blkdevIO
|
||||
ep.io.clock := clock
|
||||
ep.io.reset := reset
|
||||
ep
|
||||
}
|
||||
|
|
|
@ -16,15 +16,17 @@ class GroundTestBridge extends BlackBox
|
|||
}
|
||||
|
||||
object GroundTestBridge {
|
||||
def apply(success: Bool)(implicit p: Parameters): GroundTestBridge = {
|
||||
def apply(clock: Clock, success: Bool)(implicit p: Parameters): GroundTestBridge = {
|
||||
val bridge = Module(new GroundTestBridge)
|
||||
bridge.io.success := success
|
||||
bridge.io.clock := clock
|
||||
bridge
|
||||
}
|
||||
}
|
||||
|
||||
class GroundTestBridgeTargetIO extends Bundle {
|
||||
val success = Input(Bool())
|
||||
val clock = Input(Clock())
|
||||
}
|
||||
|
||||
class GroundTestBridgeModule(implicit p: Parameters)
|
||||
|
|
|
@ -18,9 +18,10 @@ class SerialBridge extends BlackBox with Bridge[HostPortIO[SerialBridgeTargetIO]
|
|||
}
|
||||
|
||||
object SerialBridge {
|
||||
def apply(port: SerialIO)(implicit p: Parameters): SerialBridge = {
|
||||
def apply(clock: Clock, port: SerialIO)(implicit p: Parameters): SerialBridge = {
|
||||
val ep = Module(new SerialBridge)
|
||||
ep.io.serial <> port
|
||||
ep.io.clock := clock
|
||||
ep
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +29,7 @@ object SerialBridge {
|
|||
class SerialBridgeTargetIO extends Bundle {
|
||||
val serial = Flipped(new SerialIO(testchipip.SerialAdapter.SERIAL_IF_WIDTH))
|
||||
val reset = Input(Bool())
|
||||
val clock = Input(Clock())
|
||||
}
|
||||
|
||||
class SerialBridgeModule(implicit p: Parameters) extends BridgeModule[HostPortIO[SerialBridgeTargetIO]]()(p) {
|
||||
|
|
|
@ -24,17 +24,24 @@ import TokenQueueConsts._
|
|||
|
||||
case object LoopbackNIC extends Field[Boolean](false)
|
||||
|
||||
class NICBridge(implicit p: Parameters) extends BlackBox with Bridge[HostPortIO[NICIOvonly], SimpleNICBridgeModule] {
|
||||
val io = IO(Flipped(new NICIOvonly))
|
||||
class NICTargetIO extends Bundle {
|
||||
val clock = Input(Clock())
|
||||
val nic = Flipped(new NICIOvonly)
|
||||
}
|
||||
|
||||
class NICBridge(implicit p: Parameters) extends BlackBox with Bridge[HostPortIO[NICTargetIO], SimpleNICBridgeModule] {
|
||||
val io = IO(new NICTargetIO)
|
||||
val bridgeIO = HostPort(io)
|
||||
val constructorArg = None
|
||||
generateAnnotations()
|
||||
}
|
||||
|
||||
|
||||
object NICBridge {
|
||||
def apply(nicIO: NICIOvonly)(implicit p: Parameters): NICBridge = {
|
||||
def apply(clock: Clock, nicIO: NICIOvonly)(implicit p: Parameters): NICBridge = {
|
||||
val ep = Module(new NICBridge)
|
||||
ep.io <> nicIO
|
||||
ep.io.nic <> nicIO
|
||||
ep.io.clock := clock
|
||||
ep
|
||||
}
|
||||
}
|
||||
|
@ -174,10 +181,10 @@ class HostToNICTokenGenerator(nTokens: Int)(implicit p: Parameters) extends Modu
|
|||
when (seedDone) { state := s_forward }
|
||||
}
|
||||
|
||||
class SimpleNICBridgeModule(implicit p: Parameters) extends BridgeModule[HostPortIO[NICIOvonly]]()(p)
|
||||
class SimpleNICBridgeModule(implicit p: Parameters) extends BridgeModule[HostPortIO[NICTargetIO]]()(p)
|
||||
with BidirectionalDMA {
|
||||
val io = IO(new WidgetIO)
|
||||
val hPort = IO(HostPort(Flipped(new NICIOvonly)))
|
||||
val hPort = IO(HostPort(new NICTargetIO))
|
||||
// DMA mixin parameters
|
||||
lazy val fromHostCPUQueueDepth = TOKEN_QUEUE_DEPTH
|
||||
lazy val toHostCPUQueueDepth = TOKEN_QUEUE_DEPTH
|
||||
|
@ -190,7 +197,7 @@ class SimpleNICBridgeModule(implicit p: Parameters) extends BridgeModule[HostPor
|
|||
val bigtokenToNIC = Module(new BigTokenToNICTokenAdapter)
|
||||
val NICtokenToBig = Module(new NICTokenToBigTokenAdapter)
|
||||
|
||||
val target = hPort.hBits
|
||||
val target = hPort.hBits.nic
|
||||
val tFireHelper = DecoupledHelper(hPort.toHost.hValid,
|
||||
hPort.fromHost.hReady)
|
||||
val tFire = tFireHelper.fire
|
||||
|
|
|
@ -11,46 +11,70 @@ import freechips.rocketchip.rocket.TracedInstruction
|
|||
import freechips.rocketchip.subsystem.RocketTilesKey
|
||||
import freechips.rocketchip.tile.TileKey
|
||||
|
||||
import testchipip.{TraceOutputTop, DeclockedTracedInstruction, TracedInstructionWidths}
|
||||
import testchipip.{TileTraceIO, DeclockedTracedInstruction, TracedInstructionWidths}
|
||||
|
||||
import midas.targetutils.TriggerSource
|
||||
import midas.widgets._
|
||||
import testchipip.{StreamIO, StreamChannel}
|
||||
import junctions.{NastiIO, NastiKey}
|
||||
import TokenQueueConsts._
|
||||
|
||||
|
||||
case class TracerVKey(
|
||||
insnWidths: Seq[TracedInstructionWidths], // Widths of variable length fields in each TI
|
||||
vecSizes: Seq[Int] // The number of insns in each vec (= max insns retired at that core)
|
||||
insnWidths: TracedInstructionWidths, // Widths of variable length fields in each TI
|
||||
vecSize: Int // The number of insns in the traced insn vec (= max insns retired at that core)
|
||||
)
|
||||
|
||||
class TracerVBridge(traceProto: Seq[Vec[DeclockedTracedInstruction]]) extends BlackBox
|
||||
with Bridge[HostPortIO[TraceOutputTop], TracerVBridgeModule] {
|
||||
val io = IO(Flipped(new TraceOutputTop(traceProto)))
|
||||
|
||||
class TracerVTargetIO(insnWidths: TracedInstructionWidths, numInsns: Int) extends Bundle {
|
||||
val trace = Input(new TileTraceIO(insnWidths, numInsns))
|
||||
val triggerCredit = Output(Bool())
|
||||
val triggerDebit = Output(Bool())
|
||||
}
|
||||
|
||||
// Warning: If you're not going to use the companion object to instantiate this
|
||||
// bridge you must call generate trigger annotations _in the parent module_.
|
||||
//
|
||||
// TODO: Generalize a mechanism to promote annotations from extracted bridges...
|
||||
class TracerVBridge(insnWidths: TracedInstructionWidths, numInsns: Int) extends BlackBox
|
||||
with Bridge[HostPortIO[TracerVTargetIO], TracerVBridgeModule] {
|
||||
val io = IO(new TracerVTargetIO(insnWidths, numInsns))
|
||||
val bridgeIO = HostPort(io)
|
||||
val constructorArg = Some(TracerVKey(io.getWidths, io.getVecSizes))
|
||||
val constructorArg = Some(TracerVKey(insnWidths, numInsns))
|
||||
generateAnnotations()
|
||||
// Use in parent module: annotates the bridge instance's ports to indicate its trigger sources
|
||||
// def generateTriggerAnnotations(): Unit = TriggerSource(io.triggerCredit, io.triggerDebit)
|
||||
def generateTriggerAnnotations(): Unit =
|
||||
TriggerSource.evenUnderReset(WireDefault(io.triggerCredit), WireDefault(io.triggerDebit))
|
||||
}
|
||||
|
||||
object TracerVBridge {
|
||||
def apply(port: TraceOutputTop)(implicit p:Parameters): Seq[TracerVBridge] = {
|
||||
val ep = Module(new TracerVBridge(port.getProto))
|
||||
ep.io <> port
|
||||
Seq(ep)
|
||||
def apply(tracedInsns: TileTraceIO)(implicit p:Parameters): TracerVBridge = {
|
||||
val ep = Module(new TracerVBridge(tracedInsns.insnWidths, tracedInsns.numInsns))
|
||||
ep.generateTriggerAnnotations
|
||||
ep.io.trace := tracedInsns
|
||||
ep
|
||||
}
|
||||
}
|
||||
|
||||
class TracerVBridgeModule(key: TracerVKey)(implicit p: Parameters) extends BridgeModule[HostPortIO[TraceOutputTop]]()(p)
|
||||
class TracerVBridgeModule(key: TracerVKey)(implicit p: Parameters) extends BridgeModule[HostPortIO[TracerVTargetIO]]()(p)
|
||||
with UnidirectionalDMAToHostCPU {
|
||||
val io = IO(new WidgetIO)
|
||||
val hPort = IO(HostPort(Flipped(TraceOutputTop(key.insnWidths, key.vecSizes))))
|
||||
val hPort = IO(HostPort(new TracerVTargetIO(key.insnWidths, key.vecSize)))
|
||||
|
||||
val tFire = hPort.toHost.hValid && hPort.fromHost.hReady
|
||||
//trigger conditions
|
||||
val traces = hPort.hBits.traces.flatten
|
||||
|
||||
// Mask off valid committed instructions when under reset
|
||||
val traces = hPort.hBits.trace.insns.map({ unmasked =>
|
||||
val masked = WireDefault(unmasked)
|
||||
masked.valid := unmasked.valid && !hPort.hBits.trace.reset
|
||||
masked
|
||||
})
|
||||
private val pcWidth = traces.map(_.iaddr.getWidth).max
|
||||
private val insnWidth = traces.map(_.insn.getWidth).max
|
||||
val cycleCountWidth = 64
|
||||
|
||||
// Set after trigger-dependent memory-mapped registers have been set, to
|
||||
// prevent spurious credits
|
||||
val initDone = genWORegInit(Wire(Bool()), "initDone", false.B)
|
||||
//Program Counter trigger value can be configured externally
|
||||
val hostTriggerPCWidthOffset = pcWidth - p(CtrlNastiKey).dataBits
|
||||
val hostTriggerPCLowWidth = if (hostTriggerPCWidthOffset > 0) p(CtrlNastiKey).dataBits else pcWidth
|
||||
|
@ -82,7 +106,7 @@ class TracerVBridgeModule(key: TracerVKey)(implicit p: Parameters) extends Bridg
|
|||
attach(hostTriggerCycleCountStartHigh, "hostTriggerCycleCountStartHigh", WriteOnly)
|
||||
attach(hostTriggerCycleCountStartLow, "hostTriggerCycleCountStartLow", WriteOnly)
|
||||
val hostTriggerCycleCountStart = Cat(hostTriggerCycleCountStartHigh, hostTriggerCycleCountStartLow)
|
||||
val triggerCycleCountStart = RegInit(0.U(64.W))
|
||||
val triggerCycleCountStart = RegInit(0.U(cycleCountWidth.W))
|
||||
triggerCycleCountStart := hostTriggerCycleCountStart
|
||||
|
||||
val hostTriggerCycleCountEndHigh = RegInit(0.U(hostTriggerCycleCountHighWidth.W))
|
||||
|
@ -90,10 +114,10 @@ class TracerVBridgeModule(key: TracerVKey)(implicit p: Parameters) extends Bridg
|
|||
attach(hostTriggerCycleCountEndHigh, "hostTriggerCycleCountEndHigh", WriteOnly)
|
||||
attach(hostTriggerCycleCountEndLow, "hostTriggerCycleCountEndLow", WriteOnly)
|
||||
val hostTriggerCycleCountEnd = Cat(hostTriggerCycleCountEndHigh, hostTriggerCycleCountEndLow)
|
||||
val triggerCycleCountEnd = RegInit(0.U(64.W))
|
||||
val triggerCycleCountEnd = RegInit(0.U(cycleCountWidth.W))
|
||||
triggerCycleCountEnd := hostTriggerCycleCountEnd
|
||||
|
||||
val trace_cycle_counter = RegInit(0.U(64.W))
|
||||
val trace_cycle_counter = RegInit(0.U(cycleCountWidth.W))
|
||||
|
||||
//target instruction type trigger (trigger through target software)
|
||||
//can configure the trigger instruction type externally though simulation driver
|
||||
|
@ -144,25 +168,21 @@ class TracerVBridgeModule(key: TracerVKey)(implicit p: Parameters) extends Bridg
|
|||
2.U -> triggerPCValVec.reduce(_ || _),
|
||||
3.U -> triggerInstValVec.reduce(_ || _)))
|
||||
|
||||
//TODO: for inter-widget triggering
|
||||
//io.trigger_out.head <> trigger
|
||||
if (p(midas.TraceTrigger)) {
|
||||
BoringUtils.addSource(trigger, s"trace_trigger")
|
||||
}
|
||||
val tFireHelper = DecoupledHelper(outgoingPCISdat.io.enq.ready, hPort.toHost.hValid, hPort.fromHost.hReady, initDone)
|
||||
|
||||
val triggerReg = RegEnable(trigger, false.B, tFireHelper.fire)
|
||||
hPort.hBits.triggerDebit := !trigger && triggerReg
|
||||
hPort.hBits.triggerCredit := trigger && !triggerReg
|
||||
|
||||
// DMA mixin parameters
|
||||
lazy val toHostCPUQueueDepth = TOKEN_QUEUE_DEPTH
|
||||
lazy val dmaSize = BigInt((BIG_TOKEN_WIDTH / 8) * TOKEN_QUEUE_DEPTH)
|
||||
|
||||
val uint_traces = (traces map (trace => Cat(trace.valid, trace.iaddr).pad(64))).reverse
|
||||
outgoingPCISdat.io.enq.bits := Cat(Cat(trace_cycle_counter,
|
||||
0.U((outgoingPCISdat.io.enq.bits.getWidth - Cat(uint_traces).getWidth - trace_cycle_counter.getWidth).W)),
|
||||
Cat(uint_traces))
|
||||
outgoingPCISdat.io.enq.bits := Cat(uint_traces :+ trace_cycle_counter.pad(64)).pad(BIG_TOKEN_WIDTH)
|
||||
|
||||
val tFireHelper = DecoupledHelper(outgoingPCISdat.io.enq.ready, hPort.toHost.hValid)
|
||||
hPort.toHost.hReady := tFireHelper.fire(hPort.toHost.hValid)
|
||||
// We don't drive tokens back to the target.
|
||||
hPort.fromHost.hValid := true.B
|
||||
hPort.fromHost.hValid := tFireHelper.fire(hPort.fromHost.hReady)
|
||||
|
||||
outgoingPCISdat.io.enq.valid := tFireHelper.fire(outgoingPCISdat.io.enq.ready, trigger)
|
||||
|
||||
|
@ -170,23 +190,13 @@ class TracerVBridgeModule(key: TracerVKey)(implicit p: Parameters) extends Bridg
|
|||
trace_cycle_counter := trace_cycle_counter + 1.U
|
||||
}
|
||||
|
||||
// This need to go on a debug switch
|
||||
//when (outgoingPCISdat.io.enq.fire()) {
|
||||
// hPort.hBits.traces.zipWithIndex.foreach({ case (bundle, bIdx) =>
|
||||
// printf("Tile %d Trace Bundle\n", bIdx.U)
|
||||
// bundle.zipWithIndex.foreach({ case (insn, insnIdx) =>
|
||||
// printf(p"insn ${insnIdx}: ${insn}\n")
|
||||
// //printf(b"insn ${insnIdx}, valid: ${insn.valid}")
|
||||
// //printf(b"insn ${insnIdx}, iaddr: ${insn.iaddr}")
|
||||
// //printf(b"insn ${insnIdx}, insn: ${insn.insn}")
|
||||
// //printf(b"insn ${insnIdx}, priv: ${insn.priv}")
|
||||
// //printf(b"insn ${insnIdx}, exception: ${insn.exception}")
|
||||
// //printf(b"insn ${insnIdx}, interrupt: ${insn.interrupt}")
|
||||
// //printf(b"insn ${insnIdx}, cause: ${insn.cause}")
|
||||
// //printf(b"insn ${insnIdx}, tval: ${insn.tval}")
|
||||
// })
|
||||
// })
|
||||
//}
|
||||
attach(outgoingPCISdat.io.deq.valid && !outgoingPCISdat.io.enq.ready, "tracequeuefull", ReadOnly)
|
||||
genCRFile()
|
||||
override def genHeader(base: BigInt, sb: StringBuilder) {
|
||||
import CppGenerationUtils._
|
||||
val headerWidgetName = getWName.toUpperCase
|
||||
super.genHeader(base, sb)
|
||||
sb.append(genConstStatic(s"${headerWidgetName}_max_core_ipc", UInt32(traces.size)))
|
||||
emitClockDomainInfo(headerWidgetName, sb)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import sifive.blocks.devices.uart.{UARTPortIO, PeripheryUARTKey}
|
|||
|
||||
// DOC include start: UART Bridge Target-Side Interface
|
||||
class UARTBridgeTargetIO extends Bundle {
|
||||
val clock = Input(Clock())
|
||||
val uart = Flipped(new UARTPortIO)
|
||||
// Note this reset is optional and used only to reset target-state modelled
|
||||
// in the bridge This reset just like any other Bool included in your target
|
||||
|
@ -58,9 +59,10 @@ class UARTBridge(implicit p: Parameters) extends BlackBox
|
|||
|
||||
// DOC include start: UART Bridge Companion Object
|
||||
object UARTBridge {
|
||||
def apply(uart: UARTPortIO)(implicit p: Parameters): UARTBridge = {
|
||||
def apply(clock: Clock, uart: UARTPortIO)(implicit p: Parameters): UARTBridge = {
|
||||
val ep = Module(new UARTBridge)
|
||||
ep.io.uart <> uart
|
||||
ep.io.clock := clock
|
||||
ep
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,13 +57,13 @@ class WithILATopWiringTransform extends Config((site, here, up) => {
|
|||
|
||||
// Implements the AutoCounter performace counters features
|
||||
class WithAutoCounter extends Config((site, here, up) => {
|
||||
case midas.TraceTrigger => true
|
||||
case TargetTransforms => ((p: Parameters) => Seq(new midas.passes.AutoCounterTransform()(p))) +: up(TargetTransforms, site)
|
||||
case midas.EnableAutoCounter => true
|
||||
})
|
||||
|
||||
class WithAutoCounterPrintf extends Config((site, here, up) => {
|
||||
case midas.EnableAutoCounter => true
|
||||
case midas.AutoCounterUsePrintfImpl => true
|
||||
case midas.SynthPrints => true
|
||||
case TargetTransforms => ((p: Parameters) => Seq(new midas.passes.AutoCounterTransform(printcounter = true)(p))) +: up(TargetTransforms, site)
|
||||
})
|
||||
|
||||
class BaseF1Config extends Config(
|
||||
|
|
|
@ -15,33 +15,52 @@
|
|||
#include <sys/mman.h>
|
||||
|
||||
autocounter_t::autocounter_t(
|
||||
simif_t *sim, std::vector<std::string> &args, AUTOCOUNTERBRIDGEMODULE_struct * mmio_addrs, AddressMap addr_map, int autocounterno) : bridge_driver_t(sim), addr_map(addr_map)
|
||||
{
|
||||
this->mmio_addrs = mmio_addrs;
|
||||
simif_t *sim,
|
||||
std::vector<std::string> &args,
|
||||
AUTOCOUNTERBRIDGEMODULE_struct * mmio_addrs,
|
||||
AddressMap addr_map,
|
||||
const char* const clock_domain_name,
|
||||
const unsigned int clock_multiplier,
|
||||
const unsigned int clock_divisor,
|
||||
int autocounterno) :
|
||||
bridge_driver_t(sim),
|
||||
mmio_addrs(mmio_addrs),
|
||||
addr_map(addr_map),
|
||||
clock_info(clock_domain_name, clock_multiplier, clock_divisor) {
|
||||
|
||||
this->readrate = 0;
|
||||
this->autocounter_filename = "AUTOCOUNTER";
|
||||
const char *autocounter_filename_in = NULL;
|
||||
std::string num_equals = std::to_string(autocounterno) + std::string("=");
|
||||
std::string readrate_arg = std::string("+autocounter-readrate") + num_equals;
|
||||
std::string filename_arg = std::string("+autocounter-filename") + num_equals;
|
||||
std::string readrate_arg = std::string("+autocounter-readrate=");
|
||||
std::string filename_arg = std::string("+autocounter-filename=");
|
||||
|
||||
for (auto &arg: args) {
|
||||
if (arg.find(readrate_arg) == 0) {
|
||||
char *str = const_cast<char*>(arg.c_str()) + readrate_arg.length();
|
||||
this->readrate = atol(str);;
|
||||
uint64_t base_cycles = atol(str);
|
||||
this->readrate = this->clock_info.to_local_cycles(base_cycles);
|
||||
// TODO: Just fix this in the bridge by not sampling with a fixed frequency
|
||||
if (this->clock_info.to_base_cycles(this->readrate) != base_cycles) {
|
||||
fprintf(stderr,
|
||||
"[AutoCounter] Warning: requested sample rate of %llu [base] cycles does not map to a whole number\n\
|
||||
of cycles in clock domain: %s, (%d/%d) of base clock.\n",
|
||||
base_cycles, this->clock_info.domain_name,
|
||||
this->clock_info.multiplier, this->clock_info.divisor);
|
||||
fprintf(stderr, "[AutoCounter] Workaround: Pick a sample rate that is divisible by all clock divisors.\n");
|
||||
}
|
||||
|
||||
}
|
||||
if (arg.find(filename_arg) == 0) {
|
||||
autocounter_filename_in = const_cast<char*>(arg.c_str()) + filename_arg.length();
|
||||
this->autocounter_filename = std::string(autocounter_filename_in);
|
||||
this->autocounter_filename = std::string(autocounter_filename_in) + std::to_string(autocounterno);
|
||||
}
|
||||
}
|
||||
|
||||
autocounter_file.open(this->autocounter_filename, std::ofstream::out);
|
||||
if(!autocounter_file.is_open()) {
|
||||
//throw std::runtime_error("Could not open autocounter output file\n");
|
||||
throw std::runtime_error("Could not open output file: " + this->autocounter_filename);
|
||||
}
|
||||
this->clock_info.emit_file_header(autocounter_file);
|
||||
}
|
||||
|
||||
autocounter_t::~autocounter_t() {
|
||||
|
@ -50,67 +69,46 @@ autocounter_t::~autocounter_t() {
|
|||
|
||||
void autocounter_t::init() {
|
||||
cur_cycle = 0;
|
||||
|
||||
write(addr_map.w_registers.at("readrate_low"), readrate & ((1ULL << 32) - 1));
|
||||
// Decrement the readrate by one to simplify the HW a little bit
|
||||
write(addr_map.w_registers.at("readrate_low"), (readrate - 1) & ((1ULL << 32) - 1));
|
||||
write(addr_map.w_registers.at("readrate_high"), this->readrate >> 32);
|
||||
write(addr_map.w_registers.at("readdone"), 1);
|
||||
|
||||
write(mmio_addrs->init_done, 1);
|
||||
}
|
||||
|
||||
bool autocounter_t::drain_sample() {
|
||||
bool bridge_has_sample = read(addr_map.r_registers.at("countersready"));
|
||||
|
||||
if (bridge_has_sample) {
|
||||
cur_cycle = read(this->mmio_addrs->cycles_low);
|
||||
cur_cycle |= ((uint64_t)read(this->mmio_addrs->cycles_high)) << 32;
|
||||
autocounter_file << "Cycle " << cur_cycle << std::endl;
|
||||
autocounter_file << "============================" << std::endl;
|
||||
for (auto pair: addr_map.r_registers) {
|
||||
|
||||
std::string low_prefix = std::string("autocounter_low_");
|
||||
std::string high_prefix = std::string("autocounter_high_");
|
||||
|
||||
if (pair.first.find("autocounter_low_") == 0) {
|
||||
char *str = const_cast<char*>(pair.first.c_str()) + low_prefix.length();
|
||||
std::string countername(str);
|
||||
uint64_t counter_val = ((uint64_t) (read(addr_map.r_registers.at(high_prefix + countername)))) << 32;
|
||||
counter_val |= read(pair.second);
|
||||
autocounter_file << "PerfCounter " << str << ": " << counter_val << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
write(addr_map.w_registers.at("readdone"), 1);
|
||||
autocounter_file << "" << std::endl;
|
||||
}
|
||||
return bridge_has_sample;
|
||||
}
|
||||
|
||||
void autocounter_t::tick() {
|
||||
write(addr_map.w_registers.at("readdone"), 0);
|
||||
if (read(addr_map.r_registers.at("countersready"))) {
|
||||
write(addr_map.w_registers.at("readdone"), 1);
|
||||
cur_cycle = read(this->mmio_addrs->cycles_low);
|
||||
cur_cycle |= ((uint64_t)read(this->mmio_addrs->cycles_high)) << 32;
|
||||
autocounter_file << "Cycle " << cur_cycle << std::endl;
|
||||
autocounter_file << "============================" << std::endl;
|
||||
for (auto pair: addr_map.r_registers) {
|
||||
|
||||
std::string low_prefix = std::string("autocounter_low_");
|
||||
std::string high_prefix = std::string("autocounter_high_");
|
||||
|
||||
if (pair.first.find("autocounter_low_") == 0) {
|
||||
char *str = const_cast<char*>(pair.first.c_str()) + low_prefix.length();
|
||||
std::string countername(str);
|
||||
uint64_t counter_val = ((uint64_t) (read(addr_map.r_registers.at(high_prefix + countername)))) << 32;
|
||||
counter_val |= read(pair.second);
|
||||
autocounter_file << "PerfCounter " << str << ": " << counter_val << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
write(addr_map.w_registers.at("readdone"), 1);
|
||||
autocounter_file << "" << std::endl;
|
||||
}
|
||||
drain_sample();
|
||||
}
|
||||
|
||||
|
||||
void autocounter_t::finish() {
|
||||
write(addr_map.w_registers.at("readdone"), 0);
|
||||
if (read(addr_map.r_registers.at("countersready"))) {
|
||||
write(addr_map.w_registers.at("readdone"), 1);
|
||||
cur_cycle = read(this->mmio_addrs->cycles_low);
|
||||
cur_cycle |= ((uint64_t)read(this->mmio_addrs->cycles_high)) << 32;
|
||||
autocounter_file << "Cycle " << cur_cycle << std::endl;
|
||||
autocounter_file << "============================" << std::endl;
|
||||
for (auto pair: addr_map.r_registers) {
|
||||
|
||||
std::string low_prefix = std::string("autocounter_low_");
|
||||
std::string high_prefix = std::string("autocounter_high_");
|
||||
|
||||
if (pair.first.find("autocounter_low_") == 0) {
|
||||
char *str = const_cast<char*>(pair.first.c_str()) + low_prefix.length();
|
||||
std::string countername(str);
|
||||
uint64_t counter_val = ((uint64_t) (read(addr_map.r_registers.at(high_prefix + countername)))) << 32;
|
||||
counter_val |= read(pair.second);
|
||||
autocounter_file << "PerfCounter " << str << ": " << counter_val << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
write(addr_map.w_registers.at("readdone"), 1);
|
||||
autocounter_file << "" << std::endl;
|
||||
}
|
||||
while(drain_sample());
|
||||
}
|
||||
|
||||
#endif // AUTOCOUNTERBRIDGEMODULE_struct_guard
|
||||
|
|
|
@ -3,17 +3,40 @@
|
|||
|
||||
#include "bridges/bridge_driver.h"
|
||||
#include "bridges/address_map.h"
|
||||
#include "bridges/clock_info.h"
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
// TODO: get this automatically
|
||||
#define NUM_CORES 1
|
||||
// Bridge Driver Instantiation Template
|
||||
#define INSTANTIATE_AUTOCOUNTER(FUNC,IDX) \
|
||||
AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _substruct_create; \
|
||||
FUNC(new autocounter_t( \
|
||||
this, \
|
||||
args, \
|
||||
AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _substruct, \
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _R_num_registers, \
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _R_addrs, \
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _R_names, \
|
||||
AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _W_num_registers, \
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _W_addrs, \
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _W_names), \
|
||||
AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _clock_domain_name, \
|
||||
AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _clock_multiplier, \
|
||||
AUTOCOUNTERBRIDGEMODULE_ ## IDX ## _clock_divisor, \
|
||||
IDX)); \
|
||||
|
||||
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_struct_guard
|
||||
class autocounter_t: public bridge_driver_t {
|
||||
public:
|
||||
autocounter_t(simif_t *sim, std::vector<std::string> &args,
|
||||
AUTOCOUNTERBRIDGEMODULE_struct * mmio_addrs, AddressMap addr_map, int autocounterno);
|
||||
autocounter_t(simif_t *sim,
|
||||
std::vector<std::string> &args,
|
||||
AUTOCOUNTERBRIDGEMODULE_struct * mmio_addrs,
|
||||
AddressMap addr_map,
|
||||
const char* const clock_domain_name,
|
||||
const unsigned int clock_multiplier,
|
||||
const unsigned int clock_divisor,
|
||||
int autocounterno);
|
||||
~autocounter_t();
|
||||
|
||||
virtual void init();
|
||||
|
@ -23,13 +46,18 @@ class autocounter_t: public bridge_driver_t {
|
|||
virtual void finish();
|
||||
|
||||
private:
|
||||
simif_t* sim;
|
||||
AUTOCOUNTERBRIDGEMODULE_struct * mmio_addrs;
|
||||
AddressMap addr_map;
|
||||
simif_t* sim;
|
||||
ClockInfo clock_info;
|
||||
uint64_t cur_cycle;
|
||||
uint64_t readrate;
|
||||
std::string autocounter_filename;
|
||||
std::ofstream autocounter_file;
|
||||
|
||||
// Pulls a single sample from the Bridge, if available.
|
||||
// Returns true if a sample was read
|
||||
bool drain_sample();
|
||||
};
|
||||
#endif // AUTOCOUNTERWIDGET_struct_guard
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
#ifndef __CLOCK_INFO_H
|
||||
#define __CLOCK_INFO_H
|
||||
|
||||
#include <ostream>
|
||||
|
||||
/**
|
||||
* Stores Bridge clock domain information and provides methods for converting
|
||||
* from base clock cycles to cycles in the Bridge's clock domain ("local" cycles).
|
||||
*/
|
||||
class ClockInfo
|
||||
{
|
||||
public:
|
||||
ClockInfo(
|
||||
const char* const clock_domain_name,
|
||||
const unsigned int clock_multiplier,
|
||||
const unsigned int clock_divisor):
|
||||
domain_name(clock_domain_name),
|
||||
multiplier(clock_multiplier),
|
||||
divisor(clock_divisor) {};
|
||||
|
||||
const char* const domain_name;
|
||||
const unsigned int multiplier;
|
||||
const unsigned int divisor;
|
||||
|
||||
// NB: These truncate and may be inexact, use with care
|
||||
uint64_t to_local_cycles(uint64_t base_clock_cycles) {
|
||||
return (base_clock_cycles * multiplier) / divisor;
|
||||
};
|
||||
|
||||
uint64_t to_base_cycles(uint64_t local_clock_cycles) {
|
||||
return (local_clock_cycles * divisor) / multiplier;
|
||||
};
|
||||
|
||||
// Capture clock domain info in a string that can be prepended to
|
||||
// driver-generated files so the user can disambiguate between them
|
||||
std::string file_header() {
|
||||
char buf[200];
|
||||
sprintf(buf, "# Clock Domain: %s, Relative Frequency: %d/%d of Base Clock\n",
|
||||
domain_name, multiplier, divisor);
|
||||
return std::string(buf);
|
||||
};
|
||||
|
||||
void emit_file_header(std::ostream& os) {
|
||||
os << file_header();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // __CLOCK_INFO_H
|
|
@ -4,12 +4,6 @@
|
|||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
synthesized_assertions_t::synthesized_assertions_t(simif_t* sim,
|
||||
ASSERTBRIDGEMODULE_struct * mmio_addrs): bridge_driver_t(sim) {
|
||||
this->mmio_addrs = mmio_addrs;
|
||||
};
|
||||
|
||||
synthesized_assertions_t::~synthesized_assertions_t() {
|
||||
free(this->mmio_addrs);
|
||||
}
|
||||
|
@ -17,22 +11,10 @@ synthesized_assertions_t::~synthesized_assertions_t() {
|
|||
void synthesized_assertions_t::tick() {
|
||||
if (read(this->mmio_addrs->fire)) {
|
||||
// Read assertion information
|
||||
std::vector<std::string> msgs;
|
||||
std::ifstream file(std::string(TARGET_NAME) + ".asserts");
|
||||
std::string line;
|
||||
std::ostringstream oss;
|
||||
while (std::getline(file, line)) {
|
||||
if (line == "0") {
|
||||
msgs.push_back(oss.str());
|
||||
oss.str(std::string());
|
||||
} else {
|
||||
oss << line << std::endl;
|
||||
}
|
||||
}
|
||||
assert_cycle = read(this->mmio_addrs->cycle_low);
|
||||
assert_cycle |= ((uint64_t)read(this->mmio_addrs->cycle_high)) << 32;
|
||||
assert_id = read(this->mmio_addrs->id);
|
||||
std::cerr << msgs[assert_id];
|
||||
std::cerr << this->msgs[assert_id];
|
||||
std::cerr << " at cycle: " << assert_cycle << std::endl;
|
||||
assert_fired = true;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,15 @@
|
|||
class synthesized_assertions_t: public bridge_driver_t
|
||||
{
|
||||
public:
|
||||
synthesized_assertions_t(simif_t* sim, ASSERTBRIDGEMODULE_struct * mmio_addrs);
|
||||
synthesized_assertions_t(
|
||||
simif_t* sim,
|
||||
ASSERTBRIDGEMODULE_struct * mmio_addrs,
|
||||
unsigned int num_asserts,
|
||||
const char* const* msgs) :
|
||||
bridge_driver_t(sim),
|
||||
mmio_addrs(mmio_addrs),
|
||||
num_asserts(num_asserts),
|
||||
msgs(msgs) {};
|
||||
~synthesized_assertions_t();
|
||||
virtual void init() {};
|
||||
virtual void tick();
|
||||
|
@ -21,6 +29,8 @@ class synthesized_assertions_t: public bridge_driver_t
|
|||
int assert_id;
|
||||
uint64_t assert_cycle;
|
||||
ASSERTBRIDGEMODULE_struct * mmio_addrs;
|
||||
const unsigned int num_asserts;
|
||||
const char* const* msgs;
|
||||
};
|
||||
|
||||
#endif // ASSERTBRIDGEMODULE_struct_guard
|
||||
|
|
|
@ -15,7 +15,11 @@ synthesized_prints_t::synthesized_prints_t(
|
|||
const char* const* format_strings,
|
||||
const unsigned int* argument_counts,
|
||||
const unsigned int* argument_widths,
|
||||
unsigned int dma_address):
|
||||
unsigned int dma_address,
|
||||
const char* const clock_domain_name,
|
||||
const unsigned int clock_multiplier,
|
||||
const unsigned int clock_divisor,
|
||||
int printno) :
|
||||
bridge_driver_t(sim),
|
||||
mmio_addrs(mmio_addrs),
|
||||
print_count(print_count),
|
||||
|
@ -25,17 +29,24 @@ synthesized_prints_t::synthesized_prints_t(
|
|||
format_strings(format_strings),
|
||||
argument_counts(argument_counts),
|
||||
argument_widths(argument_widths),
|
||||
dma_address(dma_address) {
|
||||
dma_address(dma_address),
|
||||
clock_info(clock_domain_name, clock_multiplier, clock_divisor),
|
||||
printno(printno) {
|
||||
assert((token_bytes & (token_bytes - 1)) == 0);
|
||||
assert(print_count > 0);
|
||||
|
||||
const char *printfilename = default_filename.c_str();
|
||||
auto printfilename = default_filename + std::to_string(printno);
|
||||
|
||||
this->start_cycle = 0;
|
||||
this->end_cycle = -1ULL;
|
||||
|
||||
std::string num_equals = std::to_string(printno) + std::string("=");
|
||||
// PlusArgs are shared across all Bridge Driver instances
|
||||
// The file into which to emit captured prinfs. This is suffixed with the driver number
|
||||
std::string printfile_arg = std::string("+print-file=");
|
||||
// The cycle at which to start printing in base clock cycles
|
||||
std::string printstart_arg = std::string("+print-start=");
|
||||
// The cycle at which to stop printing in base clock cycles
|
||||
std::string printend_arg = std::string("+print-end=");
|
||||
// Does not format the printfs, before writing them to file
|
||||
std::string binary_arg = std::string("+print-binary");
|
||||
|
@ -49,17 +60,17 @@ synthesized_prints_t::synthesized_prints_t(
|
|||
this->batch_beats = desired_batch_beats;
|
||||
}
|
||||
|
||||
for (auto &arg: args) {
|
||||
for (auto arg: args) {
|
||||
if (arg.find(printfile_arg) == 0) {
|
||||
printfilename = const_cast<char*>(arg.c_str()) + printfile_arg.length();
|
||||
printfilename = arg.erase(0, printfile_arg.length()) + std::to_string(printno);
|
||||
}
|
||||
if (arg.find(printstart_arg) == 0) {
|
||||
char *str = const_cast<char*>(arg.c_str()) + printstart_arg.length();
|
||||
this->start_cycle = atol(str);
|
||||
this->start_cycle = this->clock_info.to_local_cycles(atol(str));
|
||||
}
|
||||
if (arg.find(printend_arg) == 0) {
|
||||
char *str = const_cast<char*>(arg.c_str()) + printend_arg.length();
|
||||
this->end_cycle = atol(str);
|
||||
this->end_cycle = this->clock_info.to_local_cycles(atol(str));
|
||||
}
|
||||
if (arg.find(binary_arg) == 0) {
|
||||
human_readable = false;
|
||||
|
@ -70,13 +81,14 @@ synthesized_prints_t::synthesized_prints_t(
|
|||
}
|
||||
current_cycle = start_cycle; // We won't receive tokens until start_cycle; so fast-forward
|
||||
|
||||
this->printfile.open(printfilename, std::ios_base::out | std::ios_base::binary);
|
||||
this->printfile.open(printfilename.c_str(), std::ios_base::out | std::ios_base::binary);
|
||||
if (!this->printfile.is_open()) {
|
||||
fprintf(stderr, "Could not open print log file: %s\n", printfilename);
|
||||
abort();
|
||||
}
|
||||
|
||||
this->printstream = &(this->printfile);
|
||||
this->clock_info.emit_file_header(*(this->printstream));
|
||||
|
||||
widths.resize(print_count);
|
||||
// Used to reconstruct the relative position of arguments in the flattened argument_widths array
|
||||
|
|
|
@ -9,6 +9,27 @@
|
|||
#include <gmp.h>
|
||||
|
||||
#include "bridge_driver.h"
|
||||
#include "clock_info.h"
|
||||
|
||||
// Bridge Driver Instantiation Template
|
||||
#define INSTANTIATE_PRINTF(FUNC,IDX) \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _substruct_create; \
|
||||
FUNC(new synthesized_prints_t( \
|
||||
this, \
|
||||
args, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _substruct, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _print_count, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _token_bytes, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _idle_cycles_mask, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _print_offsets, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _format_strings, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _argument_counts, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _argument_widths, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _DMA_ADDR, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _clock_domain_name, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _clock_multiplier, \
|
||||
PRINTBRIDGEMODULE_ ## IDX ## _clock_divisor, \
|
||||
IDX)); \
|
||||
|
||||
struct print_vars_t {
|
||||
std::vector<mpz_t*> data;
|
||||
|
@ -34,7 +55,11 @@ class synthesized_prints_t: public bridge_driver_t
|
|||
const char* const* format_strings,
|
||||
const unsigned int* argument_counts,
|
||||
const unsigned int* argument_widths,
|
||||
unsigned int dma_address);
|
||||
unsigned int dma_address,
|
||||
const char* const clock_domain_name,
|
||||
const unsigned int clock_multiplier,
|
||||
const unsigned int clock_divisor,
|
||||
int printno);
|
||||
~synthesized_prints_t();
|
||||
virtual void init();
|
||||
virtual void tick();
|
||||
|
@ -52,6 +77,8 @@ class synthesized_prints_t: public bridge_driver_t
|
|||
const unsigned int* argument_counts;
|
||||
const unsigned int* argument_widths;
|
||||
const unsigned int dma_address;
|
||||
ClockInfo clock_info;
|
||||
const int printno;
|
||||
|
||||
// DMA batching parameters
|
||||
const size_t beat_bytes = DMA_DATA_BITS / 8;
|
||||
|
|
|
@ -25,6 +25,8 @@ simif_t::simif_t() {
|
|||
this->loadmem_mmio_addrs = LOADMEMWIDGET_0_substruct;
|
||||
PEEKPOKEBRIDGEMODULE_0_substruct_create;
|
||||
this->defaultiowidget_mmio_addrs = PEEKPOKEBRIDGEMODULE_0_substruct;
|
||||
CLOCKBRIDGEMODULE_0_substruct_create;
|
||||
this->clock_bridge_mmio_addrs = CLOCKBRIDGEMODULE_0_substruct;
|
||||
}
|
||||
|
||||
void simif_t::init(int argc, char** argv, bool log) {
|
||||
|
@ -57,16 +59,16 @@ void simif_t::init(int argc, char** argv, bool log) {
|
|||
}
|
||||
|
||||
uint64_t simif_t::actual_tcycle() {
|
||||
write(this->defaultiowidget_mmio_addrs->tCycle_latch, 1);
|
||||
data_t cycle_l = read(this->defaultiowidget_mmio_addrs->tCycle_0);
|
||||
data_t cycle_h = read(this->defaultiowidget_mmio_addrs->tCycle_1);
|
||||
write(this->clock_bridge_mmio_addrs->tCycle_latch, 1);
|
||||
data_t cycle_l = read(this->clock_bridge_mmio_addrs->tCycle_0);
|
||||
data_t cycle_h = read(this->clock_bridge_mmio_addrs->tCycle_1);
|
||||
return (((uint64_t) cycle_h) << 32) | cycle_l;
|
||||
}
|
||||
|
||||
uint64_t simif_t::hcycle() {
|
||||
write(this->defaultiowidget_mmio_addrs->hCycle_latch, 1);
|
||||
data_t cycle_l = read(this->defaultiowidget_mmio_addrs->hCycle_0);
|
||||
data_t cycle_h = read(this->defaultiowidget_mmio_addrs->hCycle_1);
|
||||
write(this->clock_bridge_mmio_addrs->hCycle_latch, 1);
|
||||
data_t cycle_l = read(this->clock_bridge_mmio_addrs->hCycle_0);
|
||||
data_t cycle_h = read(this->clock_bridge_mmio_addrs->hCycle_1);
|
||||
return (((uint64_t) cycle_h) << 32) | cycle_l;
|
||||
}
|
||||
|
||||
|
@ -87,7 +89,7 @@ int simif_t::finish() {
|
|||
finish_sampling();
|
||||
#endif
|
||||
|
||||
fprintf(stderr, "Runs %llu cycles\n", actual_tcycle());
|
||||
fprintf(stderr, "Ran %llu cycles (fastest target clock)\n", actual_tcycle());
|
||||
fprintf(stderr, "[%s] %s Test", pass ? "PASS" : "FAIL", TARGET_NAME);
|
||||
if (!pass) { fprintf(stdout, " at cycle %llu", fail_t); }
|
||||
fprintf(stderr, "\nSEED: %ld\n", seed);
|
||||
|
|
|
@ -41,6 +41,7 @@ class simif_t
|
|||
SIMULATIONMASTER_struct * master_mmio_addrs;
|
||||
LOADMEMWIDGET_struct * loadmem_mmio_addrs;
|
||||
PEEKPOKEBRIDGEMODULE_struct * defaultiowidget_mmio_addrs;
|
||||
CLOCKBRIDGEMODULE_struct * clock_bridge_mmio_addrs;
|
||||
midas_time_t sim_start_time;
|
||||
|
||||
inline void take_steps(size_t n, bool blocking) {
|
||||
|
@ -108,8 +109,8 @@ class simif_t
|
|||
// Returns an upper bound for the cycle reached by the target
|
||||
// If using blocking steps, this will be ~equivalent to actual_tcycle()
|
||||
uint64_t cycles(){ return t; };
|
||||
// Returns the current target cycle as measured by a hardware counter in the DefaultIOWidget
|
||||
// (# of reset tokens generated)
|
||||
// Returns the current target cycle of the fastest clock in the simulated system, based
|
||||
// on the number of clock tokens enqueued (will report a larger number)
|
||||
uint64_t actual_tcycle();
|
||||
// Returns the current host cycle as measured by a hardware counter
|
||||
uint64_t hcycle();
|
||||
|
|
|
@ -18,9 +18,26 @@ case object Platform extends Field[(Parameters) => PlatformShim]
|
|||
// Switches to synthesize prints and assertions
|
||||
case object SynthAsserts extends Field[Boolean]
|
||||
case object SynthPrints extends Field[Boolean]
|
||||
case object TraceTrigger extends Field[Boolean]
|
||||
// Exclude module instances from assertion and print synthesis
|
||||
// Tuple of Parent Module (where the instance is instantiated) and the instance name
|
||||
|
||||
// Auto Counter Switches
|
||||
case object EnableAutoCounter extends Field[Boolean](false)
|
||||
/**
|
||||
* Chooses between the two implementation strategies for Auto Counter.
|
||||
*
|
||||
* True: Synthesized Printf Implementation
|
||||
* - Generates a counter directly in the target module, adds a printf,
|
||||
* and annotates it for printf synthesis
|
||||
* - Pros: cycle-exact event resolution; counters are printed every time the event is asserted
|
||||
* - Cons: considerably more resource intensive (64-bit values are synthesized in the printf)
|
||||
* Biancolin: This seems like a waste of bandwidth? Maybe just print the message?
|
||||
*
|
||||
* False: Native Bridge Implementation (Default)
|
||||
* - Wires out each annotated event (Bool) to a dedicated AutoCounter bridge.
|
||||
* - Pros: More resource efficient;
|
||||
* - Cons: Coarse event resolution (depends on the sampling frequency set in the bridge)
|
||||
*/
|
||||
case object AutoCounterUsePrintfImpl extends Field[Boolean](false)
|
||||
|
||||
case object EnableSnapshot extends Field[Boolean]
|
||||
case object HasDMAChannel extends Field[Boolean]
|
||||
case object KeepSamplesInMem extends Field[Boolean]
|
||||
|
@ -45,7 +62,6 @@ class SimConfig extends Config((site, here, up) => {
|
|||
case DaisyWidth => 32
|
||||
case SynthAsserts => false
|
||||
case SynthPrints => false
|
||||
case TraceTrigger => false
|
||||
case EnableSnapshot => false
|
||||
case KeepSamplesInMem => true
|
||||
case CtrlNastiKey => NastiParameters(32, 32, 12)
|
||||
|
|
|
@ -5,7 +5,7 @@ package core
|
|||
|
||||
import freechips.rocketchip.config.{Parameters, Field}
|
||||
import freechips.rocketchip.unittest._
|
||||
import freechips.rocketchip.util.{DecoupledHelper}
|
||||
import freechips.rocketchip.util.{DecoupledHelper, ShiftQueue}
|
||||
import freechips.rocketchip.tilelink.LFSR64 // Better than chisel's
|
||||
|
||||
import chisel3._
|
||||
|
@ -20,36 +20,6 @@ import midas.core.SimUtils.{ChLeafType}
|
|||
// token streams irrevocably will introduce simulation non-determinism.
|
||||
case object GenerateTokenIrrevocabilityAssertions extends Field[Boolean](false)
|
||||
|
||||
// For now use the convention that clock ratios are set with respect to the transformed RTL
|
||||
trait IsRationalClockRatio {
|
||||
def numerator: Int
|
||||
def denominator: Int
|
||||
def isUnity() = numerator == denominator
|
||||
def isReciprocal() = numerator == 1
|
||||
def isIntegral() = denominator == 1
|
||||
def inverse: IsRationalClockRatio
|
||||
}
|
||||
|
||||
case class RationalClockRatio(numerator: Int, denominator: Int) extends IsRationalClockRatio {
|
||||
def inverse() = RationalClockRatio(denominator, numerator)
|
||||
}
|
||||
|
||||
case object UnityClockRatio extends IsRationalClockRatio {
|
||||
val numerator = 1
|
||||
val denominator = 1
|
||||
def inverse() = UnityClockRatio
|
||||
}
|
||||
|
||||
case class ReciprocalClockRatio(denominator: Int) extends IsRationalClockRatio {
|
||||
val numerator = 1
|
||||
def inverse = IntegralClockRatio(numerator = denominator)
|
||||
}
|
||||
|
||||
case class IntegralClockRatio(numerator: Int) extends IsRationalClockRatio {
|
||||
val denominator = 1
|
||||
def inverse = ReciprocalClockRatio(denominator = numerator)
|
||||
}
|
||||
|
||||
class PipeChannelIO[T <: ChLeafType](gen: T)(implicit p: Parameters) extends Bundle {
|
||||
val in = Flipped(Decoupled(gen))
|
||||
val out = Decoupled(gen)
|
||||
|
@ -61,15 +31,12 @@ class PipeChannelIO[T <: ChLeafType](gen: T)(implicit p: Parameters) extends Bun
|
|||
|
||||
class PipeChannel[T <: ChLeafType](
|
||||
val gen: T,
|
||||
latency: Int,
|
||||
clockRatio: IsRationalClockRatio = UnityClockRatio
|
||||
latency: Int
|
||||
)(implicit p: Parameters) extends Module {
|
||||
|
||||
require(clockRatio.isUnity)
|
||||
require(latency == 0 || latency == 1)
|
||||
|
||||
val io = IO(new PipeChannelIO(gen))
|
||||
val tokens = Module(new Queue(gen, p(ChannelLen)))
|
||||
val tokens = Module(new ShiftQueue(gen, 2))
|
||||
tokens.io.enq <> io.in
|
||||
io.out <> tokens.io.deq
|
||||
|
||||
|
@ -77,6 +44,7 @@ class PipeChannel[T <: ChLeafType](
|
|||
val initializing = RegNext(reset.toBool)
|
||||
when(initializing) {
|
||||
tokens.io.enq.valid := true.B
|
||||
tokens.io.enq.bits := 0.U.asTypeOf(tokens.io.enq.bits)
|
||||
io.in.ready := false.B
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +87,7 @@ class PipeChannelUnitTest(
|
|||
|
||||
override val testName = "PipeChannel Unit Test"
|
||||
val payloadWidth = 8
|
||||
val dut = Module(new PipeChannel(UInt(payloadWidth.W), latency, UnityClockRatio))
|
||||
val dut = Module(new PipeChannel(UInt(payloadWidth.W), latency))
|
||||
val referenceInput = Wire(UInt(payloadWidth.W))
|
||||
val referenceOutput = ShiftRegister(referenceInput, latency)
|
||||
|
||||
|
@ -232,24 +200,21 @@ class ReadyValidChannelIO[T <: Data](gen: T)(implicit p: Parameters) extends Bun
|
|||
class ReadyValidChannel[T <: Data](
|
||||
gen: T,
|
||||
n: Int = 2, // Target queue depth
|
||||
// Clock ratio (N/M) of deq interface (N) vs enq interface (M)
|
||||
clockRatio: IsRationalClockRatio = UnityClockRatio
|
||||
)(implicit p: Parameters) extends Module {
|
||||
require(clockRatio.isUnity, "CDC is not currently implemented")
|
||||
|
||||
val io = IO(new ReadyValidChannelIO(gen))
|
||||
val enqFwdQ = Module(new Queue(ValidIO(gen), 2, flow = true))
|
||||
val enqFwdQ = Module(new ShiftQueue(ValidIO(gen), 2, flow = true))
|
||||
enqFwdQ.io.enq.bits.valid := io.enq.target.valid
|
||||
enqFwdQ.io.enq.bits.bits := io.enq.target.bits
|
||||
enqFwdQ.io.enq.valid := io.enq.fwd.hValid
|
||||
io.enq.fwd.hReady := enqFwdQ.io.enq.ready
|
||||
|
||||
val deqRevQ = Module(new Queue(Bool(), 2, flow = true))
|
||||
val deqRevQ = Module(new ShiftQueue(Bool(), 2, flow = true))
|
||||
deqRevQ.io.enq.bits := io.deq.target.ready
|
||||
deqRevQ.io.enq.valid := io.deq.rev.hValid
|
||||
io.deq.rev.hReady := deqRevQ.io.enq.ready
|
||||
|
||||
val reference = Module(new Queue(gen, n))
|
||||
val reference = Module(new ShiftQueue(gen, n))
|
||||
val deqFwdFired = RegInit(false.B)
|
||||
val enqRevFired = RegInit(false.B)
|
||||
|
||||
|
@ -300,13 +265,13 @@ class ReadyValidChannelUnitTest(
|
|||
queueDepth: Int = 2,
|
||||
timeout: Int = 50000
|
||||
)(implicit p: Parameters) extends UnitTest(timeout) {
|
||||
override val testName = "PipeChannel ClockRatio: ${clockRatio.numerator}/${clockRatio.denominator}"
|
||||
override val testName = "PipeChannel"
|
||||
|
||||
val payloadType = UInt(8.W)
|
||||
val resetLength = 4
|
||||
|
||||
val dut = Module(new ReadyValidChannel(payloadType))
|
||||
val reference = Module(new Queue(payloadType, queueDepth))
|
||||
val reference = Module(new ShiftQueue(payloadType, queueDepth))
|
||||
|
||||
// Generates target-reset tokens
|
||||
def resetTokenGen(): Bool = {
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.core
|
||||
|
||||
import freechips.rocketchip.tilelink.LFSR64 // Better than chisel's
|
||||
|
||||
import chisel3._
|
||||
import chisel3.util._
|
||||
|
||||
trait ClockUtils {
|
||||
// Assume time is measured in ps
|
||||
val timeStepBits = 32
|
||||
|
||||
}
|
||||
|
||||
class GenericClockCrossing[T <: Data](gen: T) extends MultiIOModule with ClockUtils {
|
||||
val enq = IO(Flipped(Decoupled(gen)))
|
||||
val deq = IO(Decoupled(gen))
|
||||
val enqDomainTimeStep = IO(Input(UInt(timeStepBits.W)))
|
||||
val deqDomainTimeStep = IO(Input(UInt(timeStepBits.W)))
|
||||
|
||||
val enqTokens = Queue(enq, 2)
|
||||
|
||||
// Deq Domain handling
|
||||
val residualTime = Reg(UInt(timeStepBits.W))
|
||||
val hasResidualTime = RegInit(false.B)
|
||||
val timeToNextEnqEdge = Mux(hasResidualTime, residualTime, enqDomainTimeStep)
|
||||
val timeToNextDeqEdge = RegInit(0.U(timeStepBits.W))
|
||||
|
||||
val enqTokenVisible = timeToNextEnqEdge > timeToNextDeqEdge
|
||||
val tokenWouldExpire = timeToNextEnqEdge < timeToNextDeqEdge + deqDomainTimeStep
|
||||
|
||||
deq.valid := enqTokens.valid && enqTokenVisible
|
||||
deq.bits := enqTokens.bits
|
||||
enqTokens.ready := !enqTokenVisible || deq.ready && tokenWouldExpire
|
||||
|
||||
val enqTokenExpiring = enqTokens.fire
|
||||
val deqTokenReleased = deq.fire
|
||||
|
||||
// CASE 1: This ENQ token is visible in the current deq token, but not visible in future DEQ tokens
|
||||
// ENQ N | ENQ N1 |
|
||||
// ... | DEQ M | DEQ M1 |
|
||||
when (enqTokenExpiring && deqTokenReleased) {
|
||||
hasResidualTime := false.B
|
||||
timeToNextDeqEdge := timeToNextDeqEdge + deqDomainTimeStep - timeToNextEnqEdge
|
||||
// Case 2: This ENQ token is no longer visible, generally Fast -> Slow)
|
||||
// ENQ N | ENQ N+1 | ...
|
||||
// DEQ M | DEQ M+1...
|
||||
}.elsewhen(enqTokenExpiring) {
|
||||
hasResidualTime := false.B
|
||||
timeToNextDeqEdge := timeToNextDeqEdge - timeToNextEnqEdge
|
||||
// Case 3: This ENQ token is visible in the current and possibly future output tokens
|
||||
// ENQ M | ...
|
||||
// ENQ N | ENQ N+1 | ...
|
||||
}.elsewhen(deqTokenReleased) {
|
||||
hasResidualTime := true.B
|
||||
timeToNextDeqEdge := deqDomainTimeStep
|
||||
residualTime := timeToNextEnqEdge - deqDomainTimeStep
|
||||
}
|
||||
}
|
|
@ -80,4 +80,11 @@ object SimUtils {
|
|||
def parsePortsSeq(io: Seq[(String, Data)], alsoFlattenRVPorts: Boolean = true): ParsePortsTuple =
|
||||
parsePorts(io, alsoFlattenRVPorts)
|
||||
|
||||
// Returns reference to all clocks
|
||||
def findClocks(field: Data): Seq[Clock] = field match {
|
||||
case c: Clock => Seq(c)
|
||||
case b: Record => b.elements.flatMap({ case (_, field) => findClocks(field) }).toSeq
|
||||
case v: Vec[_] => v.flatMap(findClocks)
|
||||
case o => Seq()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import freechips.rocketchip.config.{Parameters, Field}
|
|||
|
||||
import chisel3._
|
||||
import chisel3.util._
|
||||
import chisel3.experimental.{Direction, ChiselAnnotation, annotate}
|
||||
import chisel3.experimental.{Direction, chiselName, ChiselAnnotation, annotate}
|
||||
import chisel3.experimental.DataMirror.directionOf
|
||||
import firrtl.annotations.{SingleTargetAnnotation, ReferenceTarget}
|
||||
|
||||
|
@ -106,22 +106,21 @@ abstract class ChannelizedWrapperIO(chAnnos: Seq[FAMEChannelConnectionAnnotation
|
|||
|
||||
val payloadTypeMap: Map[FAMEChannelConnectionAnnotation, Data] = chAnnos.collect({
|
||||
// Target Decoupled Channels need to have their target-valid ReferenceTarget removed
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,DecoupledForwardChannel(_,Some(vsrc),_,_),Some(srcs),_) =>
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,DecoupledForwardChannel(_,Some(vsrc),_,_), _, Some(srcs),_) =>
|
||||
ch -> regenPayloadType(srcs.filterNot(_ == vsrc))
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,DecoupledForwardChannel(_,_,_,Some(vsink)),_,Some(sinks)) =>
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,DecoupledForwardChannel(_,_,_,Some(vsink)), _, _, Some(sinks)) =>
|
||||
ch -> regenPayloadType(sinks.filterNot(_ == vsink))
|
||||
}).toMap
|
||||
|
||||
val wireTypeMap: Map[FAMEChannelConnectionAnnotation, ChLeafType] = chAnnos.collect({
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,fame.PipeChannel(_),Some(srcs),_) => ch -> regenWireType(srcs)
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,fame.PipeChannel(_),_,Some(sinks)) => ch -> regenWireType(sinks)
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,fame.PipeChannel(_),_,Some(srcs),_) => ch -> regenWireType(srcs)
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,fame.PipeChannel(_),_,_,Some(sinks)) => ch -> regenWireType(sinks)
|
||||
}).toMap
|
||||
|
||||
val wireElements = ArrayBuffer[(String, ReadyValidIO[Data])]()
|
||||
|
||||
|
||||
val wirePortMap: Map[String, WirePortTuple] = chAnnos.collect({
|
||||
case ch @ FAMEChannelConnectionAnnotation(globalName, fame.PipeChannel(_),sources,sinks) => {
|
||||
case ch @ FAMEChannelConnectionAnnotation(globalName, fame.PipeChannel(_), _, sources, sinks) => {
|
||||
val sinkP = sinks.map({ tRefs =>
|
||||
val name = tRefs.head.ref.stripSuffix("_bits")
|
||||
val port = Flipped(Decoupled(wireTypeMap(ch)))
|
||||
|
@ -152,7 +151,7 @@ abstract class ChannelizedWrapperIO(chAnnos: Seq[FAMEChannelConnectionAnnotation
|
|||
|
||||
// Using a channel's globalName; look up it's associated port tuple
|
||||
val rvPortMap: Map[String, TargetRVPortTuple] = chAnnos.collect({
|
||||
case ch @ FAMEChannelConnectionAnnotation(globalName, info@DecoupledForwardChannel(_,_,_,_), leafSources, leafSinks) =>
|
||||
case ch @ FAMEChannelConnectionAnnotation(globalName, info@DecoupledForwardChannel(_,_,_,_), _, leafSources, leafSinks) =>
|
||||
val sourcePortPair = leafSources.map({ tRefs =>
|
||||
require(!tRefs.isEmpty, "FIXME: Are empty decoupleds OK?")
|
||||
val validTRef: ReferenceTarget = info.validSource.getOrElse(throw new RuntimeException(
|
||||
|
@ -198,15 +197,30 @@ abstract class ChannelizedWrapperIO(chAnnos: Seq[FAMEChannelConnectionAnnotation
|
|||
val chNameToAnnoMap = chAnnos.map(anno => anno.globalName -> anno)
|
||||
}
|
||||
|
||||
class ClockRecord(numClocks: Int) extends Record {
|
||||
override val elements = ListMap(Seq.tabulate(numClocks)(i => s"_$i" -> Clock()):_*)
|
||||
override def cloneType = new ClockRecord(numClocks).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
class TargetBoxIO(val chAnnos: Seq[FAMEChannelConnectionAnnotation],
|
||||
leafTypeMap: Map[ReferenceTarget, firrtl.ir.Port])
|
||||
extends ChannelizedWrapperIO(chAnnos, leafTypeMap) {
|
||||
|
||||
val clock = Input(Clock())
|
||||
def regenClockType(refTargets: Seq[ReferenceTarget]): Data = refTargets.size match {
|
||||
case 1 => Clock()
|
||||
case size => new ClockRecord(refTargets.size)
|
||||
}
|
||||
|
||||
val clockElement: (String, DecoupledIO[Data]) = chAnnos.collectFirst({
|
||||
case ch @ FAMEChannelConnectionAnnotation(globalName, fame.TargetClockChannel(_), _, _, Some(sinks)) =>
|
||||
sinks.head.ref.stripSuffix("_bits") -> Flipped(Decoupled(regenClockType(sinks)))
|
||||
}).get
|
||||
|
||||
val hostClock = Input(Clock())
|
||||
val hostReset = Input(Bool())
|
||||
override val elements = ListMap((wireElements ++ rvElements):_*) ++
|
||||
override val elements = ListMap((Seq(clockElement) ++ wireElements ++ rvElements):_*) ++
|
||||
// Untokenized ports
|
||||
ListMap("clock" -> clock, "hostReset" -> hostReset)
|
||||
ListMap("hostClock" -> hostClock, "hostReset" -> hostReset)
|
||||
override def cloneType: this.type = new TargetBoxIO(chAnnos, leafTypeMap).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
|
@ -220,7 +234,14 @@ class SimWrapperChannels(val chAnnos: Seq[FAMEChannelConnectionAnnotation],
|
|||
leafTypeMap: Map[ReferenceTarget, firrtl.ir.Port])
|
||||
extends ChannelizedWrapperIO(chAnnos, leafTypeMap) {
|
||||
|
||||
override val elements = ListMap((wireElements ++ rvElements):_*)
|
||||
def regenClockType(refTargets: Seq[ReferenceTarget]): Vec[Bool] = Vec(refTargets.size, Bool())
|
||||
|
||||
val clockElement: (String, DecoupledIO[Vec[Bool]]) = chAnnos.collectFirst({
|
||||
case ch @ FAMEChannelConnectionAnnotation(globalName, fame.TargetClockChannel(_), _, _, Some(sinks)) =>
|
||||
sinks.head.ref.stripSuffix("_bits") -> Flipped(Decoupled(regenClockType(sinks)))
|
||||
}).get
|
||||
|
||||
override val elements = ListMap((Seq(clockElement) ++ wireElements ++ rvElements):_*)
|
||||
override def cloneType: this.type = new SimWrapperChannels(chAnnos, bridgeAnnos, leafTypeMap).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
|
@ -236,8 +257,8 @@ class SimWrapper(config: SimWrapperConfig)(implicit val p: Parameters) extends M
|
|||
// Remove all FCAs that are loopback channels. All non-loopback FCAs connect
|
||||
// to bridges and will be presented in the SimWrapper's IO
|
||||
val bridgeChAnnos = chAnnos.collect({
|
||||
case fca @ FAMEChannelConnectionAnnotation(_,_,_,None) => fca
|
||||
case fca @ FAMEChannelConnectionAnnotation(_,_,None,_) => fca
|
||||
case fca @ FAMEChannelConnectionAnnotation(_,_,_,_,None) => fca
|
||||
case fca @ FAMEChannelConnectionAnnotation(_,_,_,None,_) => fca
|
||||
})
|
||||
|
||||
val channelPorts = IO(new SimWrapperChannels(bridgeChAnnos, bridgeAnnos, leafTypeMap))
|
||||
|
@ -249,7 +270,7 @@ class SimWrapper(config: SimWrapperConfig)(implicit val p: Parameters) extends M
|
|||
})
|
||||
|
||||
target.io.hostReset := reset.toBool
|
||||
target.io.clock := clock
|
||||
target.io.hostClock := clock
|
||||
import chisel3.ExplicitCompileOptions.NotStrict // FIXME
|
||||
|
||||
def getPipeChannelType(chAnno: FAMEChannelConnectionAnnotation): ChLeafType = {
|
||||
|
@ -279,6 +300,17 @@ class SimWrapper(config: SimWrapperConfig)(implicit val p: Parameters) extends M
|
|||
channel
|
||||
}
|
||||
|
||||
@chiselName
|
||||
def genClockChannel(chAnno: FAMEChannelConnectionAnnotation): Unit = {
|
||||
val clockTokens = channelPorts.clockElement._2
|
||||
target.io.clockElement._2.valid := clockTokens.valid
|
||||
clockTokens.ready := target.io.clockElement._2.ready
|
||||
target.io.clockElement._2.bits match {
|
||||
case port: Clock => port := clockTokens.bits(0).asClock
|
||||
case port: ClockRecord => port.elements.zip(clockTokens.bits).foreach({ case ((_, p), i) => p := i.asClock})
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions to attach legacy SimReadyValidIO to true, dual-channel implementations of target ready-valid
|
||||
def bindRVChannelEnq[T <: Data](enq: SimReadyValidIO[T], port: TargetRVPortType): Unit = {
|
||||
val (fwdPort, revPort) = port
|
||||
|
@ -314,12 +346,6 @@ class SimWrapper(config: SimWrapperConfig)(implicit val p: Parameters) extends M
|
|||
def genReadyValidChannel(chAnno: FAMEChannelConnectionAnnotation): ReadyValidChannel[Data] = {
|
||||
val chName = chAnno.globalName
|
||||
val strippedName = chName.stripSuffix("_fwd")
|
||||
// Determine which bridge this channel belongs to by looking it up with the valid
|
||||
//val bridgeClockRatio = io.bridges.find(_(rvInterface.valid)) match {
|
||||
// case Some(bridge) => bridge.clockRatio
|
||||
// case None => UnityClockRatio
|
||||
//}
|
||||
val bridgeClockRatio = UnityClockRatio // TODO: FIXME
|
||||
// A channel is considered "flipped" if it's sunk by the tranformed RTL (sourced by an bridge)
|
||||
val channel = Module(new ReadyValidChannel(getReadyValidChannelType(chAnno).cloneType))
|
||||
|
||||
|
@ -346,11 +372,16 @@ class SimWrapper(config: SimWrapperConfig)(implicit val p: Parameters) extends M
|
|||
|
||||
// Generate all ready-valid channels
|
||||
val rvChannels = chAnnos.collect({
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,fame.DecoupledForwardChannel(_,_,_,_),_,_) => genReadyValidChannel(ch)
|
||||
case ch @ FAMEChannelConnectionAnnotation(_,fame.DecoupledForwardChannel(_,_,_,_),_,_,_) => genReadyValidChannel(ch)
|
||||
})
|
||||
|
||||
// Generate all wire channels, excluding reset
|
||||
chAnnos.collect({
|
||||
case ch @ FAMEChannelConnectionAnnotation(name, fame.PipeChannel(latency),_,_) => genPipeChannel(ch, latency)
|
||||
case ch @ FAMEChannelConnectionAnnotation(name, fame.PipeChannel(latency),_,_,_) => genPipeChannel(ch, latency)
|
||||
})
|
||||
|
||||
// Generate clock channels
|
||||
val clockChannels = chAnnos.collect({case ch @ FAMEChannelConnectionAnnotation(_, fame.TargetClockChannel(_),_,_,_) => ch })
|
||||
require(clockChannels.size == 1)
|
||||
genClockChannel(clockChannels.head)
|
||||
}
|
||||
|
|
|
@ -166,6 +166,7 @@ class FuncModelProgrammableRegs extends Bundle with HasProgrammableRegisters {
|
|||
class FASEDTargetIO(implicit val p: Parameters) extends Bundle {
|
||||
val axi4 = Flipped(new NastiIO)
|
||||
val reset = Input(Bool())
|
||||
val clock = Input(Clock())
|
||||
}
|
||||
|
||||
class MemModelIO(implicit val p: Parameters) extends WidgetIO()(p){
|
||||
|
@ -566,9 +567,10 @@ class FASEDBridge(argument: CompleteConfig)(implicit p: Parameters)
|
|||
}
|
||||
|
||||
object FASEDBridge {
|
||||
def apply(axi4: AXI4Bundle, reset: Bool, cfg: CompleteConfig)(implicit p: Parameters): FASEDBridge = {
|
||||
def apply(clock: Clock, axi4: AXI4Bundle, reset: Bool, cfg: CompleteConfig)(implicit p: Parameters): FASEDBridge = {
|
||||
val ep = Module(new FASEDBridge(cfg)(p.alterPartial({ case NastiKey => cfg.axi4Widths })))
|
||||
ep.io.reset := reset
|
||||
ep.io.clock := clock
|
||||
import chisel3.ExplicitCompileOptions.NotStrict
|
||||
ep.io.axi4 <> axi4
|
||||
ep
|
||||
|
|
|
@ -2,9 +2,10 @@ package midas
|
|||
package passes
|
||||
|
||||
import java.io.{File, FileWriter, Writer}
|
||||
import scala.collection.mutable
|
||||
|
||||
import firrtl._
|
||||
import firrtl.annotations.{CircuitName, ModuleName, ComponentName, ModuleTarget}
|
||||
import firrtl.annotations._
|
||||
import firrtl.ir._
|
||||
import firrtl.Mappers._
|
||||
import firrtl.WrappedExpression._
|
||||
|
@ -23,16 +24,18 @@ private[passes] class AssertPass(
|
|||
(implicit p: Parameters) extends firrtl.Transform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = HighForm
|
||||
override def name = "[MIDAS] Assertion Synthesis"
|
||||
override def name = "[Golden Gate] Assertion Synthesis"
|
||||
|
||||
type Asserts = collection.mutable.HashMap[String, (Int, String)]
|
||||
type Asserts = collection.mutable.HashMap[String, (Int, String, String)]
|
||||
type Messages = collection.mutable.HashMap[Int, String]
|
||||
|
||||
private val asserts = collection.mutable.HashMap[String, Asserts]()
|
||||
private val messages = collection.mutable.HashMap[String, Messages]()
|
||||
private val assertPorts = collection.mutable.HashMap[String, Port]()
|
||||
private val assertPorts = collection.mutable.HashMap[String, (Port, Seq[ReferenceTarget])]()
|
||||
private val excludeInstAsserts = collection.mutable.HashSet[(String, String)]()
|
||||
|
||||
private def getNameOrCroak(ns: Namespace, name: String): String = { assert(ns.tryName(name)); name }
|
||||
|
||||
// Helper method to filter out module instances
|
||||
private def excludeInst(excludes: Seq[(String, String)])
|
||||
(parentMod: String, inst: String): Boolean =
|
||||
|
@ -44,11 +47,16 @@ private[passes] class AssertPass(
|
|||
namespace: Namespace)
|
||||
(s: Statement): Statement =
|
||||
s map synAsserts(mname, namespace) match {
|
||||
case s: Stop if s.ret != 0 && !weq(s.en, zero) =>
|
||||
case Stop(info, ret, clk, en) if ret != 0 && !weq(en, zero) =>
|
||||
val idx = asserts(mname).size
|
||||
val name = namespace newName s"assert_$idx"
|
||||
asserts(mname)(s.en.serialize) = idx -> name
|
||||
DefNode(s.info, name, s.en)
|
||||
val clockName = clk match {
|
||||
case Reference(name, _) => name
|
||||
case WRef(name, _, _, _) => name
|
||||
case o => throw new RuntimeException(s"$clk")
|
||||
}
|
||||
asserts(mname)(en.serialize) = (idx, name, clockName)
|
||||
DefNode(info, name, en)
|
||||
case s => s
|
||||
}
|
||||
|
||||
|
@ -56,12 +64,12 @@ private[passes] class AssertPass(
|
|||
private def findMessages(mname: String)
|
||||
(s: Statement): Statement =
|
||||
s map findMessages(mname) match {
|
||||
// work around a large design assert build failure
|
||||
// work around a large design assert build failure
|
||||
// drop arguments and just show the format string
|
||||
//case s: Print if s.args.isEmpty =>
|
||||
case s: Print =>
|
||||
asserts(mname) get s.en.serialize match {
|
||||
case Some((idx, str)) =>
|
||||
case Some((idx, str, _)) =>
|
||||
messages(mname)(idx) = s.string.serialize
|
||||
EmptyStmt
|
||||
case _ => s
|
||||
|
@ -69,17 +77,12 @@ private[passes] class AssertPass(
|
|||
case s => s
|
||||
}
|
||||
|
||||
private def transform(meta: StroberMetaData)
|
||||
(m: DefModule): DefModule = {
|
||||
val namespace = Namespace(m)
|
||||
asserts(m.name) = new Asserts
|
||||
messages(m.name) = new Messages
|
||||
|
||||
def getChildren(ports: collection.mutable.Map[String, Port],
|
||||
private class ModuleAssertInfo(m: Module, meta: StroberMetaData, mT: ModuleTarget) {
|
||||
def getChildren(ports: collection.mutable.Map[String, (Port, Seq[ReferenceTarget])],
|
||||
instExcludes: collection.mutable.HashSet[(String, String)]) = {
|
||||
(meta.childInsts(m.name)
|
||||
.filterNot(instName => excludeInst(instExcludes.toSeq)(m.name, instName))
|
||||
.foldRight(Seq[(String, Port)]())((x, res) =>
|
||||
.foldRight(Seq[(String, (Port, Seq[ReferenceTarget]))]())((x, res) =>
|
||||
ports get meta.instModMap(x -> m.name) match {
|
||||
case None => res
|
||||
case Some(p) => res :+ (x -> p)
|
||||
|
@ -88,90 +91,155 @@ private[passes] class AssertPass(
|
|||
)
|
||||
}
|
||||
|
||||
(m map synAsserts(m.name, namespace)
|
||||
map findMessages(m.name)) match {
|
||||
case m: Module =>
|
||||
val ports = collection.mutable.ArrayBuffer[Port]()
|
||||
val stmts = collection.mutable.ArrayBuffer[Statement]()
|
||||
// Connect asserts
|
||||
val assertChildren = getChildren(assertPorts, excludeInstAsserts)
|
||||
val assertWidth = asserts(m.name).size + ((assertChildren foldLeft 0)(
|
||||
(res, x) => res + firrtl.bitWidth(x._2.tpe).toInt))
|
||||
if (assertWidth > 0) {
|
||||
val tpe = UIntType(IntWidth(assertWidth))
|
||||
val port = Port(NoInfo, namespace.newName("midasAsserts"), Output, tpe)
|
||||
val stmt = Connect(NoInfo, WRef(port.name), cat(
|
||||
(assertChildren map (x => wsub(wref(x._1), x._2.name))) ++
|
||||
(asserts(m.name).values.toSeq sortWith (_._1 > _._1) map (x => wref(x._2)))))
|
||||
assertPorts(m.name) = port
|
||||
ports += port
|
||||
stmts += stmt
|
||||
}
|
||||
m.copy(ports = m.ports ++ ports.toSeq, body = Block(m.body +: stmts.toSeq))
|
||||
case m: ExtModule => m
|
||||
}
|
||||
val assertChildren = getChildren(assertPorts, excludeInstAsserts)
|
||||
val assertWidth = asserts(m.name).size + ((assertChildren foldLeft 0)({
|
||||
case (sum, (_, (assertP, _))) => sum + firrtl.bitWidth(assertP.tpe).toInt
|
||||
}))
|
||||
|
||||
def hasAsserts(): Boolean = assertWidth > 0
|
||||
|
||||
// Get references to assertion ports on all child instances
|
||||
lazy val (childAsserts, childAssertClocksRTs): (Seq[WSubField], Seq[Seq[ReferenceTarget]]) =
|
||||
(for ((childInstName, (assertPort, clockRTs)) <- assertChildren) yield {
|
||||
val childWidth = firrtl.bitWidth(assertPort.tpe).toInt
|
||||
val assertRef = wsub(wref(childInstName), assertPort.name)
|
||||
val clockRefs = clockRTs.map(_.addHierarchy(m.name, childInstName))
|
||||
(assertRef, clockRefs)
|
||||
}).unzip
|
||||
|
||||
// Get references to all module-local synthesized assertions
|
||||
val sortedLocalAsserts = asserts(m.name).values.toSeq.sortWith (_._1 > _._1)
|
||||
val (localAsserts, localClocks) = sortedLocalAsserts.map({ case (_, en, clk) => (wref(en), mT.ref(clk)) }).unzip
|
||||
|
||||
def allAsserts = childAsserts ++ localAsserts
|
||||
def allClocks = childAssertClocksRTs.flatten ++ localClocks
|
||||
def assertUInt = UIntType(IntWidth(assertWidth))
|
||||
}
|
||||
private var assertNum = 0
|
||||
def dump(writer: Writer, meta: StroberMetaData, mod: String, path: String) {
|
||||
asserts(mod).values.toSeq sortWith (_._1 < _._1) foreach { case (idx, _) =>
|
||||
writer write s"[id: $assertNum, module: $mod, path: $path]\n"
|
||||
writer write (messages(mod)(idx) replace ("""\n""", "\n"))
|
||||
writer write "0\n"
|
||||
assertNum += 1
|
||||
|
||||
private def replaceStopsAndFindMessages(m: DefModule): DefModule = m match {
|
||||
case m: Module =>
|
||||
val namespace = Namespace(m)
|
||||
asserts(m.name) = new Asserts
|
||||
messages(m.name) = new Messages
|
||||
m.map(synAsserts(m.name, namespace))
|
||||
.map(findMessages(m.name))
|
||||
case m: ExtModule => m
|
||||
}
|
||||
|
||||
private def wireSynthesizedAssertions(meta: StroberMetaData, cT: CircuitTarget)
|
||||
(m: DefModule): DefModule = m match {
|
||||
case m: Module =>
|
||||
val ports = collection.mutable.ArrayBuffer[Port]()
|
||||
val stmts = collection.mutable.ArrayBuffer[Statement]()
|
||||
val mT = cT.module(m.name)
|
||||
val mInfo = new ModuleAssertInfo(m, meta, mT)
|
||||
// Connect asserts
|
||||
if (mInfo.hasAsserts) {
|
||||
val namespace = Namespace(m)
|
||||
val tpe = mInfo.assertUInt
|
||||
val port = Port(NoInfo, namespace.newName("midasAsserts"), Output, tpe)
|
||||
val assertConnect = Connect(NoInfo, WRef(port.name), cat(mInfo.allAsserts.reverse))
|
||||
assertPorts(m.name) = (port, mInfo.allClocks)
|
||||
ports += port
|
||||
stmts += assertConnect
|
||||
}
|
||||
meta.childInsts(mod)
|
||||
.filterNot(inst => excludeInstAsserts((mod, inst)))
|
||||
.foreach(child => dump(writer, meta, meta.instModMap(child, mod), s"${path}.${child}"))
|
||||
|
||||
m.copy(ports = m.ports ++ ports.toSeq, body = Block(m.body +: stmts.toSeq))
|
||||
case m: ExtModule => m
|
||||
}
|
||||
def synthesizeAsserts(state: CircuitState): CircuitState = {
|
||||
val c = state.circuit
|
||||
|
||||
def formatMessages(meta: StroberMetaData, topModule: String): Seq[String] = {
|
||||
val formattedMessages = new mutable.ArrayBuffer[String]()
|
||||
def dump(mod: String, path: String): Unit = {
|
||||
formattedMessages ++= (asserts(mod).values.toSeq).sortWith(_._1 > _._1).map({ case (idx, _, _) =>
|
||||
s"module: $mod, path: $path]\n" + (messages(mod)(idx) replace ("""\n""", "\n"))
|
||||
})
|
||||
meta.childInsts(mod)
|
||||
.filterNot(inst => excludeInstAsserts((mod, inst)))
|
||||
.foreach(child => dump(meta.instModMap(child, mod), s"${path}.${child}"))
|
||||
}
|
||||
dump(topModule, topModule)
|
||||
formattedMessages.toSeq
|
||||
}
|
||||
|
||||
def synthesizeAsserts(state: CircuitState): CircuitState = {
|
||||
|
||||
// Step 1: Grab module-based exclusions
|
||||
state.annotations.collect {
|
||||
case a @ (_: ExcludeInstanceAssertsAnnotation) => excludeInstAsserts += a.target
|
||||
}
|
||||
|
||||
// Step 2: Replace stop statements (asserts) and find associated message
|
||||
val c = state.circuit.copy(modules = state.circuit.modules.map(replaceStopsAndFindMessages))
|
||||
val topModule = c.modules.collectFirst({ case m: Module if m.name == c.main => m }).get
|
||||
val topMT = ModuleTarget(c.main, c.main)
|
||||
val namespace = Namespace(topModule)
|
||||
|
||||
// Step 3: Wire assertions to the top-level
|
||||
val meta = StroberMetaData(c)
|
||||
val mods = postorder(c, meta)(transform(meta))
|
||||
val f = new FileWriter(new File(dir, s"${c.main}.asserts"))
|
||||
dump(f, meta, c.main, c.main)
|
||||
f.close
|
||||
val mods = postorder(c, meta)(wireSynthesizedAssertions(meta, CircuitTarget(c.main)))
|
||||
val formattedMessages = formatMessages(meta, c.main)
|
||||
|
||||
println(s"[MIDAS] total # of assertions synthesized: $assertNum")
|
||||
val mInfo = new ModuleAssertInfo(topModule, meta, topMT)
|
||||
println(s"[Golden Gate] total # of assertions synthesized: ${mInfo.assertWidth}")
|
||||
|
||||
val mName = ModuleName(c.main, CircuitName(c.main))
|
||||
val assertAnnos = if (assertNum > 0) {
|
||||
val portName = assertPorts(c.main).name
|
||||
val portRT = ModuleTarget(c.main, c.main).ref(portName)
|
||||
val fcca = FAMEChannelConnectionAnnotation(
|
||||
globalName = portName,
|
||||
channelInfo = WireChannel,
|
||||
sources = Some(Seq(portRT)),
|
||||
sinks = None)
|
||||
if (!mInfo.hasAsserts) state else {
|
||||
// Step 4: Associate each assertion with a source clock
|
||||
val postWiredState = state.copy(circuit = c.copy(modules = mods), form = MidForm)
|
||||
val loweredState = Seq(new ResolveAndCheck, new HighFirrtlToMiddleFirrtl, new MiddleFirrtlToLowFirrtl).foldLeft(postWiredState)((state, xform) => xform.transform(state))
|
||||
val finder = new ClockSourceFinder(loweredState)
|
||||
val clockMapping = mInfo.allClocks.map(cT => cT -> finder.findRootDriver(cT)).toMap
|
||||
val rootClocks = mInfo.allClocks.map(clockMapping).flatten
|
||||
|
||||
val bridgeAnno = BridgeIOAnnotation(
|
||||
target = portRT,
|
||||
widget = Some((p: Parameters) => new AssertBridgeModule(assertNum)(p)),
|
||||
channelMapping = Map("" -> portName)
|
||||
// For each clock in clock channel, list associated assert indices
|
||||
val groupedAsserts = rootClocks.zipWithIndex.groupBy(_._1).mapValues(values => values.map(_._2))
|
||||
|
||||
// Step 5: Re-wire the top-level module
|
||||
val ports = collection.mutable.ArrayBuffer[Port]()
|
||||
val stmts = collection.mutable.ArrayBuffer[Statement]()
|
||||
val assertAnnos = collection.mutable.ArrayBuffer[Annotation]()
|
||||
|
||||
// Step 5a: Connect all assertions to a single wire to match the order of our previous analysis
|
||||
val allAssertsWire = DefWire(NoInfo, "allAsserts", mInfo.assertUInt)
|
||||
val allAssertConnect = Connect(NoInfo, WRef(allAssertsWire), cat(mInfo.allAsserts.reverse))
|
||||
stmts ++= Seq(allAssertsWire, allAssertConnect)
|
||||
|
||||
// Step 5b: Generate unique ports for each clock
|
||||
for ((clockRT, asserts) <- groupedAsserts) {
|
||||
val portName = namespace.newName(s"midasAsserts_${clockRT.ref}")
|
||||
val clockPortName = namespace.newName(s"midasAsserts_${clockRT.ref}_clock")
|
||||
val tpe = UIntType(IntWidth(asserts.size))
|
||||
val port = Port(NoInfo, portName, Output, tpe)
|
||||
val clockPort = Port(NoInfo, clockPortName, Output, ClockType)
|
||||
ports ++= Seq(port, clockPort)
|
||||
val bitExtracts = asserts.map(idx => DoPrim(PrimOps.Bits, Seq(WRef(allAssertsWire)), Seq(idx, idx), UIntType(IntWidth(1))))
|
||||
val connectAsserts = Connect(NoInfo, WRef(port), cat(bitExtracts.reverse))
|
||||
val connectClock = Connect(NoInfo, WRef(clockPort), WRef(clockRT.ref))
|
||||
stmts ++= Seq(connectClock, connectAsserts)
|
||||
|
||||
// Generate the bridge Annotation
|
||||
val portRT = ModuleTarget(c.main, c.main).ref(portName)
|
||||
val clockPortRT = ModuleTarget(c.main, c.main).ref(clockPortName)
|
||||
val fcca = FAMEChannelConnectionAnnotation.source(portName, WireChannel, Some(clockPortRT), Seq(portRT))
|
||||
val assertMessages = asserts.map(formattedMessages(_))
|
||||
val bridgeAnno = BridgeIOAnnotation(
|
||||
target = portRT,
|
||||
widget = Some((p: Parameters) => new AssertBridgeModule(assertMessages)(p)),
|
||||
channelMapping = Map("" -> portName)
|
||||
)
|
||||
assertAnnos ++= Seq(fcca, bridgeAnno)
|
||||
}
|
||||
val wiredTopModule = topModule.copy(ports = topModule.ports ++ ports,
|
||||
body = Block(topModule.body +: stmts.toSeq))
|
||||
|
||||
state.copy(
|
||||
circuit = c.copy(modules = wiredTopModule +: mods.filterNot(_.name == c.main)),
|
||||
form = HighForm,
|
||||
annotations = state.annotations ++ assertAnnos
|
||||
)
|
||||
|
||||
Seq(fcca, bridgeAnno)
|
||||
} else {
|
||||
Seq()
|
||||
}
|
||||
|
||||
state.copy(
|
||||
circuit = c.copy(modules = mods),
|
||||
form = HighForm,
|
||||
annotations = state.annotations ++ assertAnnos)
|
||||
}
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
if (p(SynthAsserts)) synthesizeAsserts(state) else {
|
||||
// Still need to touch the file.
|
||||
val f = new FileWriter(new File(dir, s"${state.circuit.main}.asserts"))
|
||||
f.close
|
||||
state
|
||||
}
|
||||
if (p(SynthAsserts)) synthesizeAsserts(state) else state
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas
|
||||
package passes
|
||||
package midas.passes
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.passes._
|
||||
import firrtl.passes.wiring._
|
||||
import firrtl.Utils.throwInternalError
|
||||
import firrtl.Utils.{throwInternalError, BoolType, one}
|
||||
import firrtl.annotations._
|
||||
import firrtl.analyses.InstanceGraph
|
||||
import firrtl.transforms.TopWiring._
|
||||
import freechips.rocketchip.util.property._
|
||||
import freechips.rocketchip.util.WideCounter
|
||||
import freechips.rocketchip.config.{Parameters, Field}
|
||||
import midas.{EnableAutoCounter, AutoCounterUsePrintfImpl}
|
||||
import midas.widgets._
|
||||
import midas.targetutils._
|
||||
import midas.passes.Utils.{widx, wsub}
|
||||
import midas.passes.fame.FAMEChannelConnectionAnnotation
|
||||
import midas.passes.fame.{WireChannel, FAMEChannelConnectionAnnotation, And, Or, Negate}
|
||||
|
||||
import java.io._
|
||||
import scala.io.Source
|
||||
|
@ -31,347 +31,236 @@ class FireSimPropertyLibrary extends BasePropertyLibrary {
|
|||
def generateProperty(prop_param: BasePropertyParameters)(implicit sourceInfo: SourceInfo) {
|
||||
//requireIsHardware(prop_param.cond, "condition covered for counter is not hardware!")
|
||||
if (!(prop_param.cond.isLit) && chisel3.experimental.DataMirror.internal.isSynthesizable(prop_param.cond)) {
|
||||
annotate(new ChiselAnnotation { def toFirrtl = AutoCounterCoverAnnotation(prop_param.cond.toNamed, prop_param.label, prop_param.message) })
|
||||
dontTouch(prop_param.cond)
|
||||
dontTouch(chisel3.Module.reset)
|
||||
dontTouch(chisel3.Module.clock)
|
||||
annotate(new ChiselAnnotation {
|
||||
val implicitClock = chisel3.Module.clock
|
||||
val implicitReset = chisel3.Module.reset
|
||||
def toFirrtl = AutoCounterFirrtlAnnotation(prop_param.cond.toNamed,
|
||||
implicitClock.toNamed.toTarget,
|
||||
implicitReset.toNamed.toTarget,
|
||||
prop_param.label,
|
||||
prop_param.message,
|
||||
coverGenerated = true)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
//=========================================================================
|
||||
|
||||
|
||||
/**
|
||||
Take the annotated cover points and convert them to counters
|
||||
**/
|
||||
class AutoCounterTransform(dir: File = new File("/tmp/"), printcounter: Boolean = false)
|
||||
* Take the annotated cover points and convert them to counters
|
||||
*/
|
||||
class AutoCounterTransform(dir: File = new File("/tmp/"))
|
||||
(implicit p: Parameters) extends Transform with AutoCounterConsts {
|
||||
def inputForm: CircuitForm = LowForm
|
||||
def outputForm: CircuitForm = LowForm
|
||||
override def name = "[FireSim] AutoCounter Cover Transform"
|
||||
val newAnnos = mutable.ArrayBuffer.empty[Annotation]
|
||||
val autoCounterLabels = mutable.ArrayBuffer.empty[String]
|
||||
val autoCounterMods = mutable.ArrayBuffer.empty[Module]
|
||||
val autoCounterLabelsSourceMap = mutable.Map.empty[String, String]
|
||||
val autoCounterReadableLabels = mutable.ArrayBuffer.empty[String]
|
||||
val autoCounterReadableLabelsMap = mutable.Map.empty[String, String]
|
||||
val autoCounterPortsMap = mutable.Map.empty[Int, String]
|
||||
val autoCounterLabelsMap = mutable.Map.empty[String, String]
|
||||
def outputForm: CircuitForm = MidForm
|
||||
override def name = "[Golden Gate] AutoCounter Cover Transform"
|
||||
|
||||
private def makeCounter(label: String, hasTracerWidget: Boolean = false): CircuitState = {
|
||||
import chisel3._
|
||||
import chisel3.core.MultiIOModule
|
||||
import chisel3.experimental.ChiselAnnotation
|
||||
import chisel3.util.experimental.BoringUtils
|
||||
def countermodule() = new MultiIOModule {
|
||||
//override def desiredName = "AutoCounter"
|
||||
val in0 = IO(Input(Bool()))
|
||||
val enableTransform = p(EnableAutoCounter)
|
||||
val usePrintfImplementation = p(AutoCounterUsePrintfImpl)
|
||||
|
||||
//connect the trigger from the tracer widget
|
||||
val trigger = WireDefault(true.B)
|
||||
hasTracerWidget match {
|
||||
case true => BoringUtils.addSink(trigger, s"trace_trigger")
|
||||
case _ => trigger := true.B
|
||||
}
|
||||
|
||||
//*****if we ever want to use the trigger to reset the counters******
|
||||
//withReset(reset = ~trigger) {
|
||||
// val countfun = WideCounter(64, in0)
|
||||
//}
|
||||
|
||||
val countfun = WideCounter(64, in0)
|
||||
val count = Wire(UInt(64.W))
|
||||
count := countfun
|
||||
if (printcounter) {
|
||||
when (in0 & trigger) {
|
||||
printf(midas.targetutils.SynthesizePrintf(s"[AutoCounter] $label: %d\n",count))
|
||||
}
|
||||
} else {
|
||||
chisel3.core.dontTouch(count)
|
||||
chisel3.experimental.annotate(new ChiselAnnotation { def toFirrtl = TopWiringAnnotation(count.toNamed, s"autocounter_") })
|
||||
//********In the future, when BoringUtils will be more rubust with TargetRefs***********
|
||||
//autoCounterLabels ++= Seq(s"AutoCounter_$label")
|
||||
//BoringUtils.addSource(count, s"AutoCounter_$label")
|
||||
}
|
||||
}
|
||||
|
||||
val chiselIR = chisel3.Driver.elaborate(() => countermodule())
|
||||
val annos = chiselIR.annotations.map(_.toFirrtl)
|
||||
val firrtlIR = chisel3.Driver.toFirrtl(chiselIR)
|
||||
val lowFirrtlIR = (new LowFirrtlCompiler()).compile(CircuitState(firrtlIR, ChirrtlForm, annos), Seq())
|
||||
lowFirrtlIR
|
||||
// Gates each auto-counter event with the associated reset, moving the
|
||||
// annotation's target to point at the new boolean (updatedAnnos).
|
||||
// This is used in both implementation strategies.
|
||||
private def gateEventsWithReset(coverTupleAnnoMap: Map[String, Seq[AutoCounterFirrtlAnnotation]],
|
||||
updatedAnnos: mutable.ArrayBuffer[AutoCounterFirrtlAnnotation])
|
||||
(mod: DefModule): DefModule = mod match {
|
||||
case m: Module if coverTupleAnnoMap.isDefinedAt(m.name) =>
|
||||
val coverAnnos = coverTupleAnnoMap(m.name)
|
||||
val mT = coverAnnos.head.enclosingModuleTarget
|
||||
val moduleNS = Namespace(mod)
|
||||
val addedStmts = coverAnnos.flatMap({ anno =>
|
||||
val eventName = moduleNS.newName(anno.label)
|
||||
updatedAnnos += anno.copy(target = mT.ref(eventName))
|
||||
Seq(DefWire(NoInfo, eventName, BoolType),
|
||||
Connect(NoInfo, WRef(eventName), And(Negate(WRef(anno.reset.ref)), WRef(anno.target.ref))))
|
||||
})
|
||||
m.copy(body = Block(m.body, addedStmts:_*))
|
||||
case o => o
|
||||
}
|
||||
|
||||
private def onModule(topNS: Namespace, covertuples: Seq[(ReferenceTarget, String)], hasTracerWidget: Boolean = false)(mod: Module): Seq[Module] = {
|
||||
private def onModulePrintfImpl(coverTupleAnnoMap: Map[String, Seq[AutoCounterFirrtlAnnotation]],
|
||||
addedAnnos: mutable.ArrayBuffer[Annotation])
|
||||
(mod: DefModule): DefModule = mod match {
|
||||
case m: Module if coverTupleAnnoMap.isDefinedAt(m.name) =>
|
||||
val coverAnnos = coverTupleAnnoMap(m.name)
|
||||
val mT = coverAnnos.head.enclosingModuleTarget
|
||||
val moduleNS = Namespace(mod)
|
||||
val addedStmts = new mutable.ArrayBuffer[Statement]
|
||||
|
||||
val namespace = Namespace(mod)
|
||||
val resetRef = mod.ports.collectFirst { case Port(_,"reset",_,_) => WRef("reset") }
|
||||
val countType = UIntType(IntWidth(64))
|
||||
val zeroLit = UIntLiteral(0, IntWidth(64))
|
||||
val oneLit = UIntLiteral(1, IntWidth(64))
|
||||
|
||||
//need some mutable lists
|
||||
val newMods = mutable.ArrayBuffer.empty[Module]
|
||||
val newInsts = mutable.ArrayBuffer.empty[WDefInstance]
|
||||
val newCons = mutable.ArrayBuffer.empty[Connect]
|
||||
addedStmts ++= coverAnnos.flatMap({ case AutoCounterFirrtlAnnotation(target, clock, reset, label, _, _) =>
|
||||
val countName = moduleNS.newName(label + "_counter")
|
||||
val count = DefRegister(NoInfo, countName, countType, WRef(clock.ref), WRef(reset.ref), zeroLit)
|
||||
val plusOneName = moduleNS.newName(label + "_plusOne")
|
||||
val plusOne = DefNode(NoInfo, plusOneName, DoPrim(PrimOps.Add, Seq(WRef(count), oneLit), Seq.empty, countType))
|
||||
val countUpdate = Connect(NoInfo, WRef(count), Mux(WRef(target.ref), WRef(plusOne), WRef(count), countType))
|
||||
|
||||
//for each annotated signal within this module
|
||||
covertuples.foreach { case (target, label) =>
|
||||
//create counter
|
||||
val countermodstate = makeCounter(label, hasTracerWidget = hasTracerWidget)
|
||||
val countermod = countermodstate.circuit.modules match {
|
||||
case Seq(one: firrtl.ir.Module) => one
|
||||
case other => throwInternalError(s"Invalid resulting modules ${other.map(_.name)}")
|
||||
}
|
||||
//add to new modules list that will be added to the circuit
|
||||
val newmodulename = topNS.newName(countermod.name)
|
||||
val countermodn = countermod.copy(name = newmodulename)
|
||||
val maincircuitname = target.circuitOpt.get
|
||||
val renamemap = RenameMap(Map(ModuleTarget(countermodstate.circuit.main, countermod.name) -> Seq(ModuleTarget(maincircuitname, newmodulename))))
|
||||
newMods += countermodn
|
||||
autoCounterMods += countermodn
|
||||
autoCounterLabelsMap += countermodn.name -> label
|
||||
newAnnos ++= countermodstate.annotations.toSeq.flatMap { case anno => anno.update(renamemap) }
|
||||
//instantiate the counter
|
||||
val instName = namespace.newName(s"autocounter_" + label) // Helps debug
|
||||
val inst = WDefInstance(NoInfo, instName, countermodn.name, UnknownType)
|
||||
//add to new instances list that will be added to the block
|
||||
newInsts += inst
|
||||
// Generate a trigger sink and annotate it
|
||||
val triggerName = moduleNS.newName("trigger")
|
||||
val trigger = DefWire(NoInfo, triggerName, BoolType)
|
||||
addedStmts ++= Seq(trigger, Connect(NoInfo, WRef(trigger), one))
|
||||
addedAnnos += TriggerSinkAnnotation(mT.ref(triggerName), clock)
|
||||
|
||||
//create input connection to the counter
|
||||
val wcons = {
|
||||
val lhs = WSubField(WRef(inst.name),"in0")
|
||||
val rhs = WRef(target.name)
|
||||
Connect(NoInfo, lhs, rhs)
|
||||
}
|
||||
newCons += wcons
|
||||
|
||||
val clocks = mod.ports.collect({ case Port(_,name,_,ClockType) => name})
|
||||
//create clock connection to the counter
|
||||
val clkCon = {
|
||||
val lhs = WSubField(WRef(inst.name), "clock")
|
||||
val rhs = WRef(clocks(0))
|
||||
Connect(NoInfo, lhs, rhs)
|
||||
}
|
||||
newCons += clkCon
|
||||
|
||||
//create reset connection to the counter
|
||||
val reset = resetRef.getOrElse(UIntLiteral(0, IntWidth(1)))
|
||||
val resetCon = Connect(NoInfo, WSubField(WRef(inst.name), "reset"), reset)
|
||||
newCons += resetCon
|
||||
}
|
||||
|
||||
//add new block of statements to the module (with the new instantiations and connections)
|
||||
val bodyx = Block(mod.body +: (newInsts ++ newCons))
|
||||
Seq(mod.copy(body = bodyx)) ++ newMods
|
||||
}
|
||||
|
||||
private def fixupCircuit(instate: CircuitState): CircuitState = {
|
||||
val xforms = Seq(
|
||||
//new WiringTransform,
|
||||
new ResolveAndCheck
|
||||
)
|
||||
(xforms foldLeft instate)((in, xform) =>
|
||||
xform runTransform in).copy(form=outputForm)
|
||||
}
|
||||
|
||||
//create the appropriate perf counters target widget
|
||||
private def makeAutoCounterWidget(topNS: Namespace, numCounters: Int, maincircuit: Circuit, hasTracerWidget: Boolean = false): (ExtModule, Seq[Annotation]) = {
|
||||
val bridgeName = topNS.newName("AutoCounterBridge")
|
||||
val bridgeMT = ModuleTarget(maincircuit.main, bridgeName)
|
||||
val ioName = "counters"
|
||||
val portList = Seq.tabulate(numCounters)(idx =>
|
||||
Port(NoInfo, s"${ioName}_${idx}", Input, UIntType(IntWidth(counterWidth))))
|
||||
val extModule = ExtModule(NoInfo, bridgeName, portList, bridgeName, Seq())
|
||||
val channelAnnos = portList.map(port =>
|
||||
FAMEChannelConnectionAnnotation.source(port.name, fame.WireChannel, Seq(bridgeMT.ref(port.name))))
|
||||
val bridgeCtorArg = AutoCounterBridgeConstArgs(numCounters, autoCounterPortsMap, hasTracerWidget)
|
||||
val bridgeAnnotation = InMemoryBridgeAnnotation(bridgeMT, channelAnnos.map(_.globalName),
|
||||
(p: Parameters) => new AutoCounterBridgeModule(bridgeCtorArg)(p))
|
||||
(extModule, bridgeAnnotation +: channelAnnos)
|
||||
// Now emit a printf using all the generated hardware
|
||||
val printFormat = StringLit(s"""[AutoCounter] $label: %d\n""")
|
||||
val printStmt = Print(NoInfo, printFormat, Seq(WRef(count)),
|
||||
WRef(clock.ref), And(WRef(trigger), WRef(target.ref)))
|
||||
addedAnnos += SynthPrintfAnnotation(Seq(Seq(mT.ref(countName))), mT, printFormat.string, Some(target.ref + "_print"))
|
||||
Seq(count, plusOne, printStmt, countUpdate)
|
||||
})
|
||||
m.copy(body = Block(m.body, addedStmts:_*))
|
||||
case o => o
|
||||
}
|
||||
|
||||
private def CreateTopCounterSources(instancepaths: Seq[Seq[WDefInstance]], state: CircuitState, topnamespace: Namespace): Seq[Statement] = {
|
||||
private def implementViaPrintf(
|
||||
state: CircuitState,
|
||||
eventModuleMap: Map[String, Seq[AutoCounterFirrtlAnnotation]]): CircuitState = {
|
||||
|
||||
instancepaths.flatMap { case instpath =>
|
||||
val instpathnames = instpath.map {case WDefInstance(_,name,_,_) => name}
|
||||
val path = instpathnames.tail.tail.mkString("_")
|
||||
val portname = s"autocounter_" + path + "_count"
|
||||
val fullportname = instpathnames.tail.head + "." + portname
|
||||
val mod = instpath.last.module // WDefInstance.module is a string, should be equivalent to the module name
|
||||
val oldlabel = autoCounterLabelsMap(mod)
|
||||
val readablelabel = oldlabel + s"[" + instpathnames.dropRight(1).mkString(".") + s"]"
|
||||
val newlabel = oldlabel + s"_" + instpathnames.dropRight(1).mkString("_")
|
||||
val addedAnnos = new mutable.ArrayBuffer[Annotation]()
|
||||
val updatedModules = state.circuit.modules.map(
|
||||
onModulePrintfImpl(eventModuleMap, addedAnnos))
|
||||
state.copy(circuit = state.circuit.copy(modules = updatedModules),
|
||||
annotations = state.annotations ++ addedAnnos)
|
||||
}
|
||||
|
||||
//When the wiring transform gets fixed
|
||||
//=======================================================
|
||||
//newAnnos += SourceAnnotation(ModuleTarget(state.circuit.main, instpath.head.module).ref(fullportname).toNamed, s"AutoCounter_$newlabel")
|
||||
//=======================================================
|
||||
//Instead of wiring transform, manually connect the counter wire source from the DUT side
|
||||
val sourceref = WSubField(WRef(instpathnames.tail.head), portname)
|
||||
val wirename = topnamespace.newName(newlabel)
|
||||
val medref = WRef(wirename)
|
||||
autoCounterLabelsSourceMap += newlabel -> wirename
|
||||
autoCounterReadableLabelsMap += newlabel -> readablelabel
|
||||
Seq(DefWire(NoInfo, wirename, UIntType(IntWidth(64))), Connect(NoInfo, medref, sourceref))
|
||||
}
|
||||
}
|
||||
private def implementViaBridge(
|
||||
state: CircuitState,
|
||||
eventModuleMap: Map[String, Seq[AutoCounterFirrtlAnnotation]]): CircuitState = {
|
||||
|
||||
val labelMap = eventModuleMap.values.flatten.map(anno => anno.target -> anno.label).toMap
|
||||
val bridgeTopWiringAnnos = eventModuleMap.values.flatten.map(
|
||||
anno => BridgeTopWiringAnnotation(anno.target, anno.clock))
|
||||
|
||||
//count the number of generated perf counters
|
||||
//create an appropriate widget IO
|
||||
//wire the counters to the widget
|
||||
private def AddAutoCounterWidget(state: CircuitState, hasTracerWidget: Boolean = false): CircuitState = {
|
||||
// Step 1: Call BridgeTopWiring, grouping all events by their source clock
|
||||
val topWiringPrefix = "autocounter"
|
||||
val wiredState = (new BridgeTopWiring(topWiringPrefix + "_")).execute(
|
||||
state.copy(annotations = state.annotations ++ bridgeTopWiringAnnos))
|
||||
val outputAnnos = wiredState.annotations.collect({ case a: BridgeTopWiringOutputAnnotation => a })
|
||||
val groupedOutputs = outputAnnos.groupBy(_.srcClockPort)
|
||||
|
||||
//need to "remember/save" the "old/original" top level module, since the TopWiring transform
|
||||
//punches signals out all the way to the top, and we want them punched out only
|
||||
//through the DUT
|
||||
val oldinstanceGraph = new InstanceGraph(state.circuit)
|
||||
val top = oldinstanceGraph.moduleOrder.head.asInstanceOf[Module]
|
||||
// Step 2: For each group of wired events, generate associated bridge annotations
|
||||
val c = wiredState.circuit
|
||||
val topModule = c.modules.collectFirst({ case m: Module if m.name == c.main => m }).get
|
||||
val topMT = ModuleTarget(c.main, c.main)
|
||||
val topNS = Namespace(topModule)
|
||||
val addedPorts = mutable.ArrayBuffer[Port]()
|
||||
val addedStmts = mutable.ArrayBuffer[Statement]()
|
||||
|
||||
val bridgeAnnos = for ((srcClockRT, oAnnos) <- groupedOutputs.toSeq.sortBy(_._1.ref)) yield {
|
||||
val sinkClockRT = oAnnos.head.sinkClockPort
|
||||
val fccas = oAnnos.map({ anno =>
|
||||
FAMEChannelConnectionAnnotation.source(
|
||||
anno.topSink.ref,
|
||||
WireChannel,
|
||||
Some(sinkClockRT),
|
||||
Seq(anno.topSink))
|
||||
})
|
||||
|
||||
//punch out counter signals to the top
|
||||
def topwiringtransform = new TopWiringTransform
|
||||
val newstate: CircuitState = topwiringtransform.execute(state.copy(annotations = state.annotations ++ newAnnos))
|
||||
val labels = oAnnos.map({ anno =>
|
||||
val pathlessLabel = labelMap(anno.pathlessSource)
|
||||
val instPath = anno.absoluteSource.circuit +: anno.absoluteSource.asPath.map(_._1.value)
|
||||
anno.topSink.ref -> (pathlessLabel +: instPath).mkString("_")
|
||||
})
|
||||
|
||||
//cleanup the topwiring annotations
|
||||
val srcannos = newAnnos.collect {
|
||||
case a: TopWiringAnnotation => a
|
||||
}
|
||||
newAnnos --= srcannos
|
||||
// Step 2b. Manually add a boolean channel to carry the trigger signal to the bridge
|
||||
val triggerPortName = topNS.newName(s"${topWiringPrefix}_triggerEnable")
|
||||
// Introduce an extra node until TriggerWriring supports port sinks
|
||||
val triggerPortNode = topNS.newName(s"${topWiringPrefix}_triggerEnable_node")
|
||||
addedPorts += Port(NoInfo, triggerPortName, Output, BoolType)
|
||||
// In the event there are no trigger sources, default to enabled
|
||||
addedStmts ++= Seq(
|
||||
DefNode(NoInfo, triggerPortNode, one),
|
||||
Connect(NoInfo, WRef(triggerPortName), WRef(triggerPortNode)))
|
||||
val triggerPortRT = topMT.ref(triggerPortName)
|
||||
val triggerFcca = FAMEChannelConnectionAnnotation.source(
|
||||
triggerPortName,
|
||||
WireChannel,
|
||||
Some(sinkClockRT),
|
||||
Seq(triggerPortRT))
|
||||
|
||||
//Find the relevant ports/wires that were punched out to the top by finding the autocounter instances
|
||||
val circuit = newstate.circuit
|
||||
val topnamespace = Namespace(circuit)
|
||||
val instanceGraph = new InstanceGraph(circuit)
|
||||
val triggerSinkAnno = TriggerSinkAnnotation(topMT.ref(triggerPortNode), sinkClockRT)
|
||||
|
||||
val autocounterinsts = autoCounterMods.flatMap { case mod => instanceGraph.findInstancesInHierarchy(mod.name) }
|
||||
|
||||
val numcounters = autocounterinsts.size
|
||||
val sourceconnections = CreateTopCounterSources(autocounterinsts, newstate, topnamespace)
|
||||
|
||||
|
||||
//create the bridge module (widget)
|
||||
val (widgetMod, bridgeAnnos) = makeAutoCounterWidget(topnamespace, numcounters, newstate.circuit, hasTracerWidget)
|
||||
|
||||
val topSort = instanceGraph.moduleOrder
|
||||
|
||||
val widgetInstName = topnamespace.newName(s"AutoCounterBridge_inst") // Helps debug
|
||||
val widgetInst = WDefInstance(NoInfo, widgetInstName, widgetMod.name, UnknownType)
|
||||
val counterPorts = widgetMod.ports.map(_.name)
|
||||
|
||||
|
||||
//When the wiring transform gets fixed
|
||||
//wiring transform annotation to connect to the counters
|
||||
//================================================
|
||||
//autoCounterLabels.zip(counterports).foreach {
|
||||
// case(label, counterport) => {
|
||||
// newAnnos += SinkAnnotation(ModuleTarget(newstate.circuit.main, newtop.name).ref(widgetInst.name).field(counterport).toNamed, label)
|
||||
// }
|
||||
//}
|
||||
//================================================
|
||||
|
||||
|
||||
//Instead of wiring transform, manually connect the counter wire sinks on the Bridge side
|
||||
val sinkconnections = autoCounterLabelsSourceMap.keys.zipWithIndex.flatMap {
|
||||
case(label,i) => {
|
||||
val sinkref = wsub(WRef(widgetInst.name), counterPorts(i))
|
||||
val medref = WRef(autoCounterLabelsSourceMap(label))
|
||||
//autoCounterPortsMap += i -> autoCounterReadableLabelsMap(label)
|
||||
autoCounterPortsMap += i -> label
|
||||
Seq(Connect(NoInfo, sinkref, medref))
|
||||
}
|
||||
}
|
||||
val newstatements = Seq(widgetInst) ++ sourceconnections ++ sinkconnections
|
||||
|
||||
//update the body of the top level module
|
||||
val bodyx = Block(top.body +: newstatements)
|
||||
val newtop = top.copy(body = bodyx)
|
||||
|
||||
newstate.copy(
|
||||
circuit = newstate.circuit.copy(modules = topSort.tail ++ Seq(newtop, widgetMod)),
|
||||
annotations = state.annotations ++ bridgeAnnos)
|
||||
}
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
|
||||
//collect annotation generate by the built in cover points in rocketchip
|
||||
//(or manually inserted annotations)
|
||||
val coverannos = state.annotations.collect {
|
||||
case a: AutoCounterCoverAnnotation => a
|
||||
val bridgeAnno = BridgeIOAnnotation(
|
||||
target = topMT.ref(topWiringPrefix),
|
||||
// We need to pass the name of the trigger port so each bridge can
|
||||
// disambiguate between them and connect to the correct one in simulation mapping
|
||||
widget = (p: Parameters) => new AutoCounterBridgeModule(labels, triggerPortName)(p),
|
||||
channelNames = (triggerFcca +: fccas).map(_.globalName)
|
||||
)
|
||||
Seq(bridgeAnno, triggerSinkAnno, triggerFcca) ++ fccas
|
||||
}
|
||||
|
||||
//collect annotations for manually annotated AutoCounter perf counters
|
||||
val autocounterannos = state.annotations.collect {
|
||||
case a: AutoCounterFirrtlAnnotation => a
|
||||
}
|
||||
val updatedCircuit = c.copy(modules = c.modules.map({
|
||||
case m: Module if m.name == c.main => m.copy(ports = m.ports ++ addedPorts, body = Block(m.body, addedStmts:_*))
|
||||
case o => o
|
||||
}))
|
||||
|
||||
//identify if there is a TracerV bridge to supply a trigger signal
|
||||
val hasTracerWidget = p(midas.TraceTrigger)
|
||||
|
||||
//----if we want to identify the tracer widget based on bridge annotations, we can use this code segment,
|
||||
//----but this requires the transform to be part of the firesim package rather than midas package
|
||||
//val hasTracerWidget = state.annotations.collect({ case midas.widgets.SerializableBridgeAnnotation(_,_,widget, _) =>
|
||||
// widget match {
|
||||
// case "firesim.bridges.TracerVBridgeModule" => true
|
||||
// case _ => Nil
|
||||
// }}).length > 0
|
||||
val cleanedAnnotations = wiredState.annotations.filterNot(outputAnnos.toSet)
|
||||
CircuitState(updatedCircuit, wiredState.form, cleanedAnnotations ++ bridgeAnnos.flatten)
|
||||
}
|
||||
|
||||
def doTransform(state: CircuitState): CircuitState = {
|
||||
//select/filter which modules do we want to actually look at, and generate counters for
|
||||
//this can be done in one of two way:
|
||||
//1. Using an input file called `covermodules.txt` in a directory declared in the transform concstructor
|
||||
//2. Using chisel annotations to be added in the Platform Config (in SimConfigs.scala). The annotations are
|
||||
// of the form AutoCounterModuleAnnotation("ModuleName")
|
||||
val modulesfile = new File(dir,"autocounter-covermodules.txt")
|
||||
val filemoduleannos = mutable.ArrayBuffer.empty[AutoCounterCoverModuleFirrtlAnnotation]
|
||||
val moduleAnnos = new mutable.ArrayBuffer[AutoCounterCoverModuleFirrtlAnnotation]()
|
||||
val counterAnnos = new mutable.ArrayBuffer[AutoCounterFirrtlAnnotation]()
|
||||
val remainingAnnos = new mutable.ArrayBuffer[Annotation]()
|
||||
if (modulesfile.exists()) {
|
||||
val sourcefile = scala.io.Source.fromFile(modulesfile.getPath())
|
||||
val covermodulesnames = (for (line <- sourcefile.getLines()) yield line).toList
|
||||
sourcefile.close()
|
||||
filemoduleannos ++= covermodulesnames.map {m: String => AutoCounterCoverModuleFirrtlAnnotation(ModuleTarget(state.circuit.main,m))}
|
||||
moduleAnnos ++= covermodulesnames.map {m: String => AutoCounterCoverModuleFirrtlAnnotation(ModuleTarget(state.circuit.main,m))}
|
||||
}
|
||||
state.annotations.foreach {
|
||||
case a: AutoCounterCoverModuleFirrtlAnnotation => moduleAnnos += a
|
||||
case a: AutoCounterFirrtlAnnotation => counterAnnos += a
|
||||
case o => remainingAnnos += o
|
||||
}
|
||||
val moduleannos = (state.annotations.collect {
|
||||
case a: AutoCounterCoverModuleFirrtlAnnotation => a
|
||||
} ++ filemoduleannos).distinct
|
||||
|
||||
//extract the module names from the methods mentioned previously
|
||||
val covermodulesnames = moduleannos.map { case AutoCounterCoverModuleFirrtlAnnotation(ModuleTarget(_,m)) => m }
|
||||
val covermodulesnames = moduleAnnos.map(_.target.module).distinct
|
||||
|
||||
if (!covermodulesnames.isEmpty) {
|
||||
println("[AutoCounter]: Cover modules in AutoCounterTransform:")
|
||||
println(covermodulesnames)
|
||||
}
|
||||
|
||||
//filter the cover annotations only by the modules that we want
|
||||
val filtercoverannos = coverannos.filter{ case AutoCounterCoverAnnotation(ReferenceTarget(_,modname,_,_,_),l,m) =>
|
||||
covermodulesnames.contains(modname) }
|
||||
|
||||
val allcounterannos = filtercoverannos ++ autocounterannos
|
||||
//group the selected signal by modules, and attach label from the cover point to each signal
|
||||
val selectedsignals = allcounterannos.map { case AutoCounterCoverAnnotation(target,l,m) => (target, l)
|
||||
case AutoCounterFirrtlAnnotation(target,l,m) => (target, l)
|
||||
}
|
||||
.groupBy { case (ReferenceTarget(_,modname,_,_,_), l) => modname }
|
||||
//collect annotations for manually annotated AutoCounter perf counters
|
||||
val filteredCounterAnnos = counterAnnos.filter(_.shouldBeIncluded(covermodulesnames))
|
||||
|
||||
// group the selected signal by modules, and attach label from the cover point to each signal
|
||||
val selectedsignals = filteredCounterAnnos.groupBy(_.enclosingModule)
|
||||
|
||||
if (!selectedsignals.isEmpty) {
|
||||
println("[AutoCounter]: AutoCounter signals are:")
|
||||
println(selectedsignals)
|
||||
println("[AutoCounter] AutoCounter signals are:")
|
||||
selectedsignals.foreach({ case (modName, localEvents) =>
|
||||
println(s" Module ${modName}")
|
||||
localEvents.foreach({ anno => println(s" ${anno.label}: ${anno.message}") })
|
||||
})
|
||||
|
||||
//create counters for each of the Bools in the filtered cover functions
|
||||
val moduleNamespace = Namespace(state.circuit)
|
||||
val modulesx: Seq[DefModule] = state.circuit.modules.map {
|
||||
case mod: Module =>
|
||||
val covertuples = selectedsignals.getOrElse(mod.name, Seq())
|
||||
if (!covertuples.isEmpty) {
|
||||
val mods = onModule(moduleNamespace, covertuples, hasTracerWidget = hasTracerWidget)(mod)
|
||||
val newMods = mods.filter(_.name != mod.name)
|
||||
assert(newMods.size + 1 == mods.size) // Sanity check
|
||||
mods
|
||||
} else { Seq(mod) }
|
||||
case ext: ExtModule => Seq(ext)
|
||||
}.flatten
|
||||
// Common preprocessing: gate all annotated events with their associated reset
|
||||
val updatedAnnos = new mutable.ArrayBuffer[AutoCounterFirrtlAnnotation]()
|
||||
val updatedModules = state.circuit.modules.map((gateEventsWithReset(selectedsignals, updatedAnnos)))
|
||||
val eventModuleMap = updatedAnnos.groupBy(_.enclosingModule)
|
||||
val preppedState = state.copy(circuit = state.circuit.copy(modules = updatedModules),
|
||||
annotations = remainingAnnos)
|
||||
|
||||
val statewithwidget = printcounter match {
|
||||
case true => state.copy(circuit = state.circuit.copy(modules = modulesx), annotations = state.annotations ++ newAnnos)
|
||||
case _ => AddAutoCounterWidget(state.copy(circuit = state.circuit.copy(modules = modulesx)), hasTracerWidget = hasTracerWidget)
|
||||
if (usePrintfImplementation) {
|
||||
implementViaPrintf(preppedState, eventModuleMap)
|
||||
} else {
|
||||
implementViaBridge(preppedState, eventModuleMap)
|
||||
}
|
||||
|
||||
fixupCircuit(statewithwidget)
|
||||
} else { state }
|
||||
}
|
||||
}
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
if (enableTransform) doTransform(state) else state
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
package midas.passes
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
import firrtl._
|
||||
import firrtl.analyses.InstanceGraph
|
||||
import firrtl.annotations._
|
||||
import firrtl.ir._
|
||||
import firrtl.Mappers._
|
||||
import firrtl.transforms.TopWiring.{TopWiringAnnotation, TopWiringTransform, TopWiringOutputFilesAnnotation}
|
||||
import TargetToken.{Instance, OfModule}
|
||||
|
||||
import midas.passes.fame.RTRenamer
|
||||
import midas.targetutils.FAMEAnnotation
|
||||
|
||||
/**
|
||||
* Provides signals for the transform to wire to the top-level of hte module hierarchy.
|
||||
*
|
||||
* @param target The signal to be plumbed to the top
|
||||
*
|
||||
* @param clock The clock to which this signal is sychronous. This will _not_ be wired.
|
||||
*
|
||||
*/
|
||||
case class BridgeTopWiringAnnotation(target: ReferenceTarget, clock: ReferenceTarget) extends Annotation with FAMEAnnotation {
|
||||
def update(renames: RenameMap): Seq[BridgeTopWiringAnnotation] =
|
||||
Seq(this.copy(RTRenamer.exact(renames)(target), RTRenamer.exact(renames)(clock)))
|
||||
|
||||
def toWiringAnnotation(prefix: String): TopWiringAnnotation = TopWiringAnnotation(target.pathlessTarget.toNamed, prefix)
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides reference targets to the newly generated top-level IO and a
|
||||
* generated output-clock port that output is synchronous to.
|
||||
*
|
||||
* @param pathlessSource The original target passed in a [[BridgeTopWiringAnnotation]]
|
||||
*
|
||||
* @param absoluteSource An absolute reference target to the particular
|
||||
* instance of that signal that drives the new output. NB: A single [[BridgeTopWiringAnnotation]]
|
||||
* will generate as many output annotations as there are instances of the pathless source.
|
||||
*
|
||||
* @param topSink The new top-level port the source has been connected to
|
||||
*
|
||||
* @param srcClockPort The input clock associated with the source
|
||||
*
|
||||
* @param clockPort The new output clock port the topSink port to be referenced in FCCAs
|
||||
*
|
||||
*/
|
||||
case class BridgeTopWiringOutputAnnotation(
|
||||
pathlessSource: ReferenceTarget,
|
||||
absoluteSource: ReferenceTarget,
|
||||
topSink: ReferenceTarget,
|
||||
srcClockPort: ReferenceTarget,
|
||||
sinkClockPort: ReferenceTarget) extends Annotation with FAMEAnnotation {
|
||||
|
||||
def update(renames: RenameMap): Seq[BridgeTopWiringOutputAnnotation] = {
|
||||
val renameExact = RTRenamer.exact(renames)
|
||||
Seq(BridgeTopWiringOutputAnnotation(renameExact(pathlessSource),
|
||||
renameExact(absoluteSource),
|
||||
renameExact(topSink),
|
||||
renameExact(srcClockPort),
|
||||
renameExact(sinkClockPort)))
|
||||
}
|
||||
}
|
||||
|
||||
object BridgeTopWiring {
|
||||
class NoClockSourceFoundException(data: ReferenceTarget, clock: ReferenceTarget)
|
||||
extends Exception(s"Could not determine the source of clock ${clock} for target-to-wire ${data}")
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility transform used to implement features that are finely distrubuted
|
||||
* through out the target, such as assertion and printf synthesis. This
|
||||
* transform preforms most of the circuit modifications and analysis to emit
|
||||
* BridgeIOAnnotations and FCCAs directly. For this pass to function
|
||||
* correctly, the clock bridge must already be extracted.
|
||||
*
|
||||
* For each BridgeTopWiringAnnotation, this transform:
|
||||
* 1) Wires out every instance of that signal to a unique port in the top-level module
|
||||
* These will be referenced by Bridge FCCAs and will become simulation channels.
|
||||
* 2) Determines the source clock (these are now inputs on the top-level module) to which each that port is synchronous
|
||||
*
|
||||
* For each clock that is synchronous with at least one output port:
|
||||
* 1) Loop that clock back to a new output port (Bridge FCCAs will point at this clock)
|
||||
*
|
||||
* Finally emit a [[BridgeTopWiringOutputAnnotation]] for each created data-output port.
|
||||
*
|
||||
* @param prefix Provides the top-wiring prefix
|
||||
*
|
||||
*/
|
||||
|
||||
class BridgeTopWiring(val prefix: String) extends firrtl.Transform {
|
||||
def inputForm = MidForm
|
||||
def outputForm = MidForm
|
||||
case class TopWiringMapping(src: ComponentName, instPath: Seq[String]) {
|
||||
// TopWiring doesn't return references to the top-level ports; we need to reconstruct those
|
||||
// from an instance path and the prefix provided
|
||||
val topMT = ModuleTarget(src.module.circuit.name, src.module.circuit.name)
|
||||
|
||||
// The RT to then new top-level port
|
||||
def portRT(): ReferenceTarget = topMT.ref(prefix + (instPath :+ src.name).mkString("_"))
|
||||
|
||||
// A complete-path reference target to the source that drives this new output
|
||||
def absoluteSourceRT(childInstGraph: Map[String, Map[Instance, OfModule]]): ReferenceTarget = {
|
||||
instPath.foldLeft[IsModule](topMT)((target, instName) => {
|
||||
val moduleName = target match {
|
||||
case iT: InstanceTarget => iT.ofModule
|
||||
case mT: ModuleTarget => mT.module
|
||||
}
|
||||
val instModuleName = childInstGraph(moduleName)(Instance(instName))
|
||||
target.instOf(instName, instModuleName.value)
|
||||
}).ref(src.name)
|
||||
}
|
||||
|
||||
def getSourceSinkPair(iMaps: Map[String, Map[Instance, OfModule]]):
|
||||
(ReferenceTarget, ReferenceTarget) = absoluteSourceRT(iMaps) -> portRT
|
||||
}
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
|
||||
require(state.annotations.collect({ case t: TopWiringAnnotation => t }).isEmpty,
|
||||
"CircuitState cannot have existing TopWiring annotations before BridgeTopWiring.")
|
||||
|
||||
val inputAnnos = state.annotations.collect({ case a: BridgeTopWiringAnnotation => a })
|
||||
val localClockMap = inputAnnos.map(anno => anno.target -> anno.clock).toMap
|
||||
|
||||
// Step 1: Invoke top wiring
|
||||
// Hacky: Instead of generated output files, instead sneak out the mappings from the TopWiring
|
||||
// transform.
|
||||
var topLevelOutputs = Seq[TopWiringMapping]()
|
||||
def wiringAnnoOutputFunc(td: String,
|
||||
mappings: Seq[((ComponentName, Type, Boolean, Seq[String], String), Int)],
|
||||
state: CircuitState): CircuitState = {
|
||||
topLevelOutputs = mappings.unzip._1.map(inscrutable5Tuple => TopWiringMapping(inscrutable5Tuple._1, inscrutable5Tuple._4.dropRight(1)))
|
||||
state
|
||||
}
|
||||
|
||||
val topWiringOFileAnno = TopWiringOutputFilesAnnotation("unused", wiringAnnoOutputFunc)
|
||||
val topWiringAnnos = topWiringOFileAnno +: inputAnnos.map(_.toWiringAnnotation(prefix)).distinct
|
||||
val wiredState = new TopWiringTransform().execute(state.copy(annotations = topWiringAnnos ++ state.annotations))
|
||||
|
||||
// Step 2: Reconstruct a map from source RT to newly wired reference target
|
||||
val instanceMaps = new InstanceGraph(wiredState.circuit).getChildrenInstanceMap
|
||||
.map({ case (m, set) => m.value -> set.toMap })
|
||||
.toMap
|
||||
// localRTtoAbsRTs
|
||||
val localToAbsSource = topLevelOutputs.groupBy(_.src).map({ case (src, mappings) =>
|
||||
val localRT = src.toTarget
|
||||
val absoluteRTs = mappings.map({ m =>
|
||||
val srcRT = m.absoluteSourceRT(instanceMaps)
|
||||
val clockRT = srcRT.copy(ref = localClockMap(localRT).ref)
|
||||
(srcRT, clockRT)
|
||||
})
|
||||
(localRT, absoluteRTs)
|
||||
}).toMap
|
||||
|
||||
val allAbsClockRTs = localToAbsSource.values.flatMap(_.unzip._2)
|
||||
|
||||
// Maps complete source RT to the portRT it has been wired to
|
||||
val absSrcToPort = topLevelOutputs.map(_.getSourceSinkPair(instanceMaps)).toMap
|
||||
|
||||
// Step 3: Do clock analysis using complete paths to clocks
|
||||
val findClockSourceAnnos = allAbsClockRTs.map(src => FindClockSourceAnnotation(src)).toSeq
|
||||
val stateToAnalyze = wiredState.copy(annotations = findClockSourceAnnos)
|
||||
val loweredState = Seq(new ResolveAndCheck,
|
||||
new MiddleFirrtlToLowFirrtl,
|
||||
FindClockSources).foldLeft(stateToAnalyze)((state, xform) => xform.transform(state))
|
||||
|
||||
val clockSourceMap = loweredState.annotations.collect({
|
||||
case ClockSourceAnnotation(qT, source) => qT -> source
|
||||
}).toMap
|
||||
|
||||
|
||||
// Step 4: Add new clock ports
|
||||
val c = wiredState.circuit
|
||||
val uniqueSources = clockSourceMap.values.flatten.toSeq.distinct
|
||||
val addedPorts = new mutable.ArrayBuffer[Port]()
|
||||
val addedConnects = new mutable.ArrayBuffer[Connect]()
|
||||
val ns = Namespace(c.modules.find(_.name == c.main).get)
|
||||
val src2sinkClockMap = (for (clock <- uniqueSources) yield {
|
||||
val portName = ns.newName(s"${prefix}${clock.ref}")
|
||||
val port = Port(NoInfo, portName, Output, ClockType)
|
||||
addedConnects += Connect(NoInfo, WRef(port), WRef(clock.ref))
|
||||
addedPorts += port
|
||||
clock -> clock.copy(ref = portName)
|
||||
}).toMap
|
||||
|
||||
val updatedCircuit = c.copy(modules = c.modules.map({
|
||||
case m: Module if m.name == c.main => m.copy(ports = m.ports ++ addedPorts,
|
||||
body = Block(m.body, addedConnects:_*))
|
||||
case o => o
|
||||
}))
|
||||
|
||||
// Step 5: Generate output annotations
|
||||
val outputAnnotations = (for ((localRT, absRTs) <- localToAbsSource) yield {
|
||||
absRTs.map { case (absSourceRT, absClockRT) =>
|
||||
clockSourceMap(absClockRT) match {
|
||||
case Some(clock) => BridgeTopWiringOutputAnnotation(localRT, absSourceRT, absSrcToPort(absSourceRT), clock, src2sinkClockMap(clock))
|
||||
case None => throw new BridgeTopWiring.NoClockSourceFoundException(absSourceRT, absClockRT)
|
||||
}
|
||||
}
|
||||
}).flatten
|
||||
|
||||
val updatedAnnotations = outputAnnotations.toSeq ++ wiredState.annotations.flatMap({
|
||||
case s: TopWiringAnnotation => None
|
||||
case s: TopWiringOutputFilesAnnotation => None
|
||||
case s: BridgeTopWiringAnnotation => None
|
||||
case o => Some(o)
|
||||
})
|
||||
|
||||
wiredState.copy(circuit = updatedCircuit, annotations = updatedAnnotations)
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes
|
||||
|
||||
import firrtl._
|
||||
import firrtl.analyses.InstanceGraph
|
||||
import firrtl.annotations.{ModuleTarget, ReferenceTarget, TargetToken}
|
||||
import firrtl.ir._
|
||||
import firrtl.Mappers._
|
||||
import fame._
|
||||
import Utils._
|
||||
|
||||
import collection.mutable
|
||||
import java.io.{File, FileWriter, StringWriter}
|
||||
|
||||
import midas.core.SimUtils._
|
||||
|
||||
private[passes] class ChannelizeTargetIO(io: Seq[(String, chisel3.Data)]) extends firrtl.Transform {
|
||||
|
||||
override def name = "[MIDAS] ChannelizeTargetIO"
|
||||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
val circuit = state.circuit
|
||||
|
||||
// From trivial channel excision
|
||||
val topName = state.circuit.main
|
||||
val topModule = state.circuit.modules.find(_.name == topName).collect({
|
||||
case m: Module => m
|
||||
}).get
|
||||
|
||||
val topModulePortMap: Map[String, Port] = topModule.ports.map({p => p.name -> p}).toMap
|
||||
|
||||
val (wireSinks, wireSources, rvSinks, rvSources) = parsePortsSeq(io, alsoFlattenRVPorts = false)
|
||||
|
||||
// Helper functions to generate annotations and ReferenceTargets
|
||||
def portRefTarget(field: String) = ReferenceTarget(circuit.main, circuit.main, Nil, field, Nil)
|
||||
|
||||
def wireSinkAnno(chName: String) =
|
||||
FAMEChannelConnectionAnnotation(chName, PipeChannel(1), None, Some(Seq(portRefTarget(chName))))
|
||||
def wireSourceAnno(chName: String) =
|
||||
FAMEChannelConnectionAnnotation(chName, PipeChannel(1), Some(Seq(portRefTarget(chName))), None)
|
||||
|
||||
def decoupledRevSinkAnno(name: String, readyTarget: ReferenceTarget) =
|
||||
FAMEChannelConnectionAnnotation(prefixWith(name, "rev"), DecoupledReverseChannel, None, Some(Seq(readyTarget)))
|
||||
def decoupledRevSourceAnno(name: String, readyTarget: ReferenceTarget) =
|
||||
FAMEChannelConnectionAnnotation(prefixWith(name, "rev"), DecoupledReverseChannel, Some(Seq(readyTarget)), None)
|
||||
|
||||
def decoupledFwdSinkAnno(chName: String,
|
||||
validTarget: ReferenceTarget,
|
||||
readyTarget: ReferenceTarget,
|
||||
leaves: Seq[ReferenceTarget]): FAMEChannelConnectionAnnotation = {
|
||||
|
||||
val chInfo = DecoupledForwardChannel(
|
||||
validSink = Some(validTarget),
|
||||
readySource = Some(readyTarget),
|
||||
validSource = None,
|
||||
readySink = None)
|
||||
FAMEChannelConnectionAnnotation(prefixWith(chName, "fwd"), chInfo, None, Some(leaves))
|
||||
}
|
||||
|
||||
def decoupledFwdSourceAnno(chName: String,
|
||||
validTarget: ReferenceTarget,
|
||||
readyTarget: ReferenceTarget,
|
||||
leaves: Seq[ReferenceTarget]): FAMEChannelConnectionAnnotation = {
|
||||
|
||||
val chInfo = DecoupledForwardChannel(
|
||||
validSource = Some(validTarget),
|
||||
readySink = Some(readyTarget),
|
||||
validSink = None,
|
||||
readySource = None)
|
||||
FAMEChannelConnectionAnnotation(prefixWith(chName, "fwd"), chInfo, Some(leaves), None)
|
||||
}
|
||||
|
||||
// Generate ReferenceTargets for the leaves in an RV payload
|
||||
def getRVLeaves(name: String, field: chisel3.Data): Seq[ReferenceTarget] = field match {
|
||||
case b: chisel3.Record =>
|
||||
b.elements.toSeq flatMap { case (n, e) => getRVLeaves(prefixWith(name, n), e) }
|
||||
case v: chisel3.Vec[_] =>
|
||||
v.zipWithIndex flatMap { case (e, i) => getRVLeaves(prefixWith(name, i), e) }
|
||||
case b: chisel3.Element => Seq(portRefTarget(name))
|
||||
case _ => throw new RuntimeException("Unexpected type in ready-valid payload")
|
||||
}
|
||||
|
||||
// Generate valid, ready, and payload reference targets for a decoupled interface
|
||||
def getRVTargets(port: chisel3.Data, name: String): (ReferenceTarget, ReferenceTarget, Seq[ReferenceTarget]) = {
|
||||
val validTarget = portRefTarget(prefixWith(name, "valid"))
|
||||
val readyTarget = portRefTarget(prefixWith(name, "ready"))
|
||||
val payloadTargets = getRVLeaves(prefixWith(name, "bits"), port)
|
||||
(validTarget, readyTarget, Seq(validTarget) ++ payloadTargets)
|
||||
}
|
||||
|
||||
def rvSinkAnnos(chTuple: RVChTuple): Seq[FAMEChannelConnectionAnnotation] = chTuple match {
|
||||
case (port, name) =>
|
||||
val (vT, rT, pTs) = getRVTargets(port.bits, name)
|
||||
Seq(decoupledFwdSinkAnno(name, vT, rT, pTs), decoupledRevSourceAnno(name, rT))
|
||||
}
|
||||
|
||||
def rvSourceAnnos(chTuple: RVChTuple): Seq[FAMEChannelConnectionAnnotation] = chTuple match {
|
||||
case (port, name) =>
|
||||
val (vT, rT, pTs) = getRVTargets(port.bits, name)
|
||||
Seq(decoupledFwdSourceAnno(name, vT, rT, pTs), decoupledRevSinkAnno(name, rT))
|
||||
}
|
||||
|
||||
val chAnnos =
|
||||
wireSinks .map({ case (_, chName) => wireSinkAnno(chName) }) ++
|
||||
wireSources.map({ case (_, chName) => wireSourceAnno(chName) }) ++
|
||||
rvSinks .flatMap(rvSinkAnnos) ++
|
||||
rvSources .flatMap(rvSourceAnnos)
|
||||
|
||||
val f1Anno = FAMETransformAnnotation(FAME1Transform, ModuleTarget(topName, topName))
|
||||
state.copy(annotations = state.annotations ++ Seq(f1Anno) ++ chAnnos)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,290 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.passes.{Errors, PassException}
|
||||
import firrtl.traversals.Foreachers._
|
||||
import firrtl.annotations._
|
||||
import firrtl.Utils.throwInternalError
|
||||
import firrtl.graph.{MutableDiGraph,DiGraph}
|
||||
import firrtl.analyses.InstanceGraph
|
||||
import firrtl.options.{RegisteredTransform, ShellOption}
|
||||
|
||||
object CheckCombLoops {
|
||||
class CombLoopException(info: Info, mname: String, cycle: Seq[String]) extends PassException(
|
||||
s"$info: [module $mname] Combinational loop detected:\n" + cycle.mkString("\n"))
|
||||
}
|
||||
|
||||
case object DontCheckCombLoopsAnnotation extends NoTargetAnnotation
|
||||
|
||||
case class ExtModulePathAnnotation(source: ReferenceTarget, sink: ReferenceTarget) extends Annotation {
|
||||
if (!source.isLocal || !sink.isLocal || source.module != sink.module) {
|
||||
throwInternalError(s"ExtModulePathAnnotation must connect two local targets from the same module")
|
||||
}
|
||||
|
||||
override def getTargets: Seq[ReferenceTarget] = Seq(source, sink)
|
||||
|
||||
override def update(renames: RenameMap): Seq[Annotation] = {
|
||||
val sources = renames.get(source).getOrElse(Seq(source))
|
||||
val sinks = renames.get(sink).getOrElse(Seq(sink))
|
||||
val paths = sources flatMap { s => sinks.map((s, _)) }
|
||||
paths.collect {
|
||||
case (source: ReferenceTarget, sink: ReferenceTarget) => ExtModulePathAnnotation(source, sink)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class CombinationalPath(sink: ReferenceTarget, sources: Seq[ReferenceTarget]) extends Annotation {
|
||||
override def update(renames: RenameMap): Seq[Annotation] = {
|
||||
val newSources = sources.flatMap { s => renames(s) }.collect {case x: ReferenceTarget if x.isLocal => x}
|
||||
val newSinks = renames(sink).collect { case x: ReferenceTarget if x.isLocal => x}
|
||||
newSinks.map(snk => CombinationalPath(snk, newSources))
|
||||
}
|
||||
}
|
||||
|
||||
case class LogicNode(name: String, inst: Option[String] = None, memport: Option[String] = None)
|
||||
|
||||
/** Finds and detects combinational logic loops in a circuit, if any exist. Returns the input circuit with no
|
||||
* modifications.
|
||||
*
|
||||
* @throws firrtl.transforms.CheckCombLoops.CombLoopException if a loop is found
|
||||
* @note Input form: Low FIRRTL
|
||||
* @note Output form: Low FIRRTL (identity transform)
|
||||
* @note The pass looks for loops through combinational-read memories
|
||||
* @note The pass relies on ExtModulePathAnnotations to find loops through ExtModules
|
||||
* @note The pass will throw exceptions on "false paths"
|
||||
*/
|
||||
class CheckCombLoops extends Transform with RegisteredTransform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
|
||||
import CheckCombLoops._
|
||||
|
||||
val options = Seq(
|
||||
new ShellOption[Unit](
|
||||
longOption = "no-check-comb-loops",
|
||||
toAnnotationSeq = (_: Unit) => Seq(DontCheckCombLoopsAnnotation),
|
||||
helpText = "Disable combinational loop checking" ) )
|
||||
|
||||
/*
|
||||
* A case class that represents a net in the circuit. This is
|
||||
* necessary since combinational loop checking is an analysis on the
|
||||
* netlist of the circuit; the fields are specialized for low
|
||||
* FIRRTL. Since all wires are ground types, a given ground type net
|
||||
* may only be a subfield of an instance or a memory
|
||||
* port. Therefore, it is uniquely specified within its module
|
||||
* context by its name, its optional parent instance (a WDefInstance
|
||||
* or WDefMemory), and its optional memory port name.
|
||||
*/
|
||||
|
||||
private type ConnectivityGraphs = mutable.HashMap[String,DiGraph[LogicNode]]
|
||||
|
||||
private def toLogicNode(e: Expression): LogicNode = e match {
|
||||
case idx: WSubIndex =>
|
||||
toLogicNode(idx.expr)
|
||||
case r: WRef =>
|
||||
LogicNode(r.name)
|
||||
case s: WSubField =>
|
||||
s.expr match {
|
||||
case modref: WRef =>
|
||||
LogicNode(s.name,Some(modref.name))
|
||||
case memport: WSubField =>
|
||||
memport.expr match {
|
||||
case memref: WRef =>
|
||||
LogicNode(s.name,Some(memref.name),Some(memport.name))
|
||||
case _ => throwInternalError(s"toLogicNode: unrecognized subsubfield expression - $memport")
|
||||
}
|
||||
case _ => throwInternalError(s"toLogicNode: unrecognized subfield expression - $s")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private def getExprDeps(deps: MutableDiGraph[LogicNode], v: LogicNode)(e: Expression): Unit = e match {
|
||||
case r: WRef => deps.addEdgeIfValid(v, toLogicNode(r))
|
||||
case s: WSubField => deps.addEdgeIfValid(v, toLogicNode(s))
|
||||
case _ => e.foreach(getExprDeps(deps, v))
|
||||
}
|
||||
|
||||
private def getStmtDeps(
|
||||
simplifiedModules: mutable.Map[String,DiGraph[LogicNode]],
|
||||
deps: MutableDiGraph[LogicNode])(s: Statement): Unit = s match {
|
||||
case Connect(_,loc,expr) =>
|
||||
val lhs = toLogicNode(loc)
|
||||
if (deps.contains(lhs)) {
|
||||
getExprDeps(deps, lhs)(expr)
|
||||
}
|
||||
case w: DefWire =>
|
||||
deps.addVertex(LogicNode(w.name))
|
||||
case n: DefNode =>
|
||||
val lhs = LogicNode(n.name)
|
||||
deps.addVertex(lhs)
|
||||
getExprDeps(deps, lhs)(n.value)
|
||||
case m: DefMemory if (m.readLatency == 0) =>
|
||||
for (rp <- m.readers) {
|
||||
val dataNode = deps.addVertex(LogicNode("data",Some(m.name),Some(rp)))
|
||||
deps.addEdge(dataNode, deps.addVertex(LogicNode("addr",Some(m.name),Some(rp))))
|
||||
deps.addEdge(dataNode, deps.addVertex(LogicNode("en",Some(m.name),Some(rp))))
|
||||
}
|
||||
case i: WDefInstance =>
|
||||
val iGraph = simplifiedModules(i.module).transformNodes(n => n.copy(inst = Some(i.name)))
|
||||
iGraph.getVertices.foreach(deps.addVertex(_))
|
||||
iGraph.getVertices.foreach({ v => iGraph.getEdges(v).foreach { deps.addEdge(v,_) } })
|
||||
case _ =>
|
||||
s.foreach(getStmtDeps(simplifiedModules,deps))
|
||||
}
|
||||
|
||||
/*
|
||||
* Recover the full path from a path passing through simplified
|
||||
* instances. Since edges may pass through simplified instances, the
|
||||
* hierarchy that the path passes through must be recursively
|
||||
* recovered.
|
||||
*/
|
||||
private def expandInstancePaths(
|
||||
m: String,
|
||||
moduleGraphs: mutable.Map[String,DiGraph[LogicNode]],
|
||||
moduleDeps: Map[String, Map[String,String]],
|
||||
prefix: Seq[String],
|
||||
path: Seq[LogicNode]): Seq[String] = {
|
||||
def absNodeName(prefix: Seq[String], n: LogicNode) =
|
||||
(prefix ++ n.inst ++ n.memport :+ n.name).mkString(".")
|
||||
val pathNodes = (path zip path.tail) map { case (a, b) =>
|
||||
if (a.inst.isDefined && !a.memport.isDefined && a.inst == b.inst) {
|
||||
val child = moduleDeps(m)(a.inst.get)
|
||||
val newprefix = prefix :+ a.inst.get
|
||||
val subpath = moduleGraphs(child).path(b.copy(inst=None),a.copy(inst=None)).tail.reverse
|
||||
expandInstancePaths(child,moduleGraphs,moduleDeps,newprefix,subpath)
|
||||
} else {
|
||||
Seq(absNodeName(prefix,a))
|
||||
}
|
||||
}
|
||||
pathNodes.flatten :+ absNodeName(prefix, path.last)
|
||||
}
|
||||
|
||||
/*
|
||||
* An SCC may contain more than one loop. In this case, the sequence
|
||||
* of nodes forming the SCC cannot be interpreted as a simple
|
||||
* cycle. However, it is desirable to print an error consisting of a
|
||||
* loop rather than an arbitrary ordering of the SCC. This function
|
||||
* operates on a pruned subgraph composed only of the SCC and finds
|
||||
* a simple cycle by performing an arbitrary walk.
|
||||
*/
|
||||
private def findCycleInSCC[T](sccGraph: DiGraph[T]): Seq[T] = {
|
||||
val walk = new mutable.ArrayBuffer[T]
|
||||
val visited = new mutable.HashSet[T]
|
||||
var current = sccGraph.getVertices.head
|
||||
while (!visited.contains(current)) {
|
||||
walk += current
|
||||
visited += current
|
||||
current = sccGraph.getEdges(current).head
|
||||
}
|
||||
walk.drop(walk.indexOf(current)).toSeq :+ current
|
||||
}
|
||||
|
||||
/*
|
||||
* This implementation of combinational loop detection avoids ever
|
||||
* generating a full netlist from the FIRRTL circuit. Instead, each
|
||||
* module is converted to a netlist and analyzed locally, with its
|
||||
* subinstances represented by trivial, simplified subgraphs. The
|
||||
* overall outline of the process is:
|
||||
*
|
||||
* 1. Create a graph of module instance dependances
|
||||
|
||||
* 2. Linearize this acyclic graph
|
||||
*
|
||||
* 3. Generate a local netlist; replace any instances with
|
||||
* simplified subgraphs representing connectivity of their IOs
|
||||
*
|
||||
* 4. Check for nontrivial strongly connected components
|
||||
*
|
||||
* 5. Create a reduced representation of the netlist with only the
|
||||
* module IOs as nodes, where output X (which must be a ground type,
|
||||
* as only low FIRRTL is supported) will have an edge to input Y if
|
||||
* and only if it combinationally depends on input Y. Associate this
|
||||
* reduced graph with the module for future use.
|
||||
*/
|
||||
private def run(state: CircuitState): (CircuitState, Errors, ConnectivityGraphs, ConnectivityGraphs) = {
|
||||
val c = state.circuit
|
||||
val errors = new Errors()
|
||||
val extModulePaths = state.annotations.groupBy {
|
||||
case ann: ExtModulePathAnnotation => ModuleTarget(c.main, ann.source.module)
|
||||
case ann: Annotation => CircuitTarget(c.main)
|
||||
}
|
||||
val moduleMap = c.modules.map({m => (m.name,m) }).toMap
|
||||
val iGraph = new InstanceGraph(c).graph
|
||||
val moduleDeps = iGraph.getEdgeMap.map({ case (k,v) => (k.module, (v map { i => (i.name, i.module) }).toMap) }).toMap
|
||||
val topoSortedModules = iGraph.transformNodes(_.module).linearize.reverse map { moduleMap(_) }
|
||||
val moduleGraphs = new ConnectivityGraphs
|
||||
val simplifiedModuleGraphs = new ConnectivityGraphs
|
||||
topoSortedModules.foreach {
|
||||
case em: ExtModule =>
|
||||
val portSet = em.ports.map(p => LogicNode(p.name)).toSet
|
||||
val extModuleDeps = new MutableDiGraph[LogicNode]
|
||||
portSet.foreach(extModuleDeps.addVertex(_))
|
||||
extModulePaths.getOrElse(ModuleTarget(c.main, em.name), Nil).collect {
|
||||
case a: ExtModulePathAnnotation => extModuleDeps.addPairWithEdge(LogicNode(a.sink.ref), LogicNode(a.source.ref))
|
||||
}
|
||||
moduleGraphs(em.name) = DiGraph(extModuleDeps).simplify(portSet)
|
||||
simplifiedModuleGraphs(em.name) = moduleGraphs(em.name)
|
||||
case m: Module =>
|
||||
val portSet = m.ports.map(p => LogicNode(p.name)).toSet
|
||||
val internalDeps = new MutableDiGraph[LogicNode]
|
||||
portSet.foreach(internalDeps.addVertex(_))
|
||||
m.foreach(getStmtDeps(simplifiedModuleGraphs, internalDeps))
|
||||
val moduleGraph = DiGraph(internalDeps)
|
||||
moduleGraphs(m.name) = moduleGraph
|
||||
simplifiedModuleGraphs(m.name) = moduleGraphs(m.name).simplify(portSet)
|
||||
// Find combinational nodes with self-edges; this is *NOT* the same as length-1 SCCs!
|
||||
for (unitLoopNode <- moduleGraph.getVertices.filter(v => moduleGraph.getEdges(v).contains(v))) {
|
||||
errors.append(new CombLoopException(m.info, m.name, Seq(unitLoopNode.name)))
|
||||
}
|
||||
for (scc <- moduleGraph.findSCCs.filter(_.length > 1)) {
|
||||
val sccSubgraph = moduleGraph.subgraph(scc.toSet)
|
||||
val cycle = findCycleInSCC(sccSubgraph)
|
||||
(cycle zip cycle.tail).foreach({ case (a,b) => require(moduleGraph.getEdges(a).contains(b)) })
|
||||
val expandedCycle = expandInstancePaths(m.name, moduleGraphs, moduleDeps, Seq(m.name), cycle.reverse)
|
||||
errors.append(new CombLoopException(m.info, m.name, expandedCycle))
|
||||
}
|
||||
case m => throwInternalError(s"Module ${m.name} has unrecognized type")
|
||||
}
|
||||
val mt = ModuleTarget(c.main, c.main)
|
||||
val annos = simplifiedModuleGraphs(c.main).getEdgeMap.collect { case (from, tos) if tos.nonEmpty =>
|
||||
val sink = mt.ref(from.name)
|
||||
val sources = tos.map(to => mt.ref(to.name))
|
||||
CombinationalPath(sink, sources.toSeq)
|
||||
}
|
||||
(state.copy(annotations = state.annotations ++ annos), errors, simplifiedModuleGraphs, moduleGraphs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map from Module name to port connectivity
|
||||
*/
|
||||
def analyze(state: CircuitState): collection.Map[String,DiGraph[String]] = {
|
||||
val (result, errors, connectivity, _) = run(state)
|
||||
connectivity.map {
|
||||
case (k, v) => (k, v.transformNodes(ln => ln.name))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map from Module name to complete netlist connectivity
|
||||
*/
|
||||
def analyzeFull(state: CircuitState): collection.Map[String,DiGraph[LogicNode]] = {
|
||||
run(state)._4
|
||||
}
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
val dontRun = state.annotations.contains(DontCheckCombLoopsAnnotation)
|
||||
if (dontRun) {
|
||||
logger.warn("Skipping Combinational Loop Detection")
|
||||
state
|
||||
} else {
|
||||
val (result, errors, connectivity, _) = run(state)
|
||||
errors.trigger()
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.annotations._
|
||||
import firrtl.annotations.TargetToken.{OfModule, Instance, Field}
|
||||
|
||||
/**
|
||||
* Contains exception classes for [[midas.passes.ClockSourceFinder]]
|
||||
*/
|
||||
object ClockSourceFinder {
|
||||
class MultipleDriversException(target: ReferenceTarget, module: String, drivers: Seq[String])
|
||||
extends Exception(s"Clock ${target} is driven by multiple signals in module ${module}: ${drivers}", null)
|
||||
}
|
||||
|
||||
/** A utility for finding the upstream drivers of arbitrary clock signals in a circuit.
|
||||
* find and return that input port.
|
||||
*
|
||||
* @param state the CircuitState to analyze
|
||||
*/
|
||||
class ClockSourceFinder(state: CircuitState) {
|
||||
import ClockSourceFinder._
|
||||
|
||||
private def inputClockNodes(m: DefModule) = m.ports.collect {
|
||||
case Port(_, name, Input, ClockType) => LogicNode(name)
|
||||
}
|
||||
|
||||
private val moduleMap = state.circuit.modules.map(m => m.name -> m).toMap
|
||||
private val inputClockNodeSets = state.circuit.modules.map(m => m.name -> inputClockNodes(m).toSet).toMap
|
||||
private lazy val connectivity = new CheckCombLoops().analyzeFull(state)
|
||||
|
||||
/** If a clock signal is directly driven by an input clock port at the top of a multi-module hierarchy,
|
||||
* find and return that input port.
|
||||
*
|
||||
* @param queryTarget the downstream clock to analyze
|
||||
* @return an option containing a "local" reference to the port in queryTarget.module that drives queryTarget, if any.
|
||||
* @note The analysis is limited to the hierarchy rooted at the root module of queryTarget
|
||||
*/
|
||||
def findRootDriver(queryTarget: ReferenceTarget): Option[ReferenceTarget] = {
|
||||
require(queryTarget.component.isEmpty)
|
||||
def getPortDriver(rT: ReferenceTarget): Option[ReferenceTarget] = {
|
||||
val portOption = rT.component.collectFirst { case Field(f) => f }
|
||||
val node = portOption.map(p => LogicNode(p, Some(rT.ref))).getOrElse(LogicNode(rT.ref))
|
||||
val (inst, module) = rT.path.lastOption.getOrElse((Instance(rT.module), OfModule(rT.module)))
|
||||
val drivingCone = connectivity(module.value).reachableFrom(node)
|
||||
val drivingPorts = (drivingCone + node) & inputClockNodeSets(module.value)
|
||||
if (drivingPorts.size == 0) {
|
||||
None
|
||||
} else if (drivingPorts.size > 1) {
|
||||
throw new MultipleDriversException(queryTarget, module.value, drivingPorts.map(_.name).toSeq)
|
||||
} else {
|
||||
val drivingPort = drivingPorts.head.name
|
||||
if (rT.path.isEmpty) {
|
||||
Some(rT.copy(ref = drivingPort, component = Nil))
|
||||
} else {
|
||||
val parentRT = rT.copy(path = rT.path.init, ref = inst.value, component = Seq(Field(drivingPort)))
|
||||
getPortDriver(parentRT)
|
||||
}
|
||||
}
|
||||
}
|
||||
getPortDriver(queryTarget)
|
||||
}
|
||||
}
|
|
@ -18,7 +18,9 @@ import java.io.{File, FileWriter, StringWriter}
|
|||
|
||||
// Ensures that there are no dangling IO on the target. All I/O coming off the DUT must be bound
|
||||
// to an Bridge BlackBox
|
||||
private[passes] class EnsureNoTargetIO extends firrtl.Transform {
|
||||
case class TargetMalformedException(message: String) extends RuntimeException(message)
|
||||
|
||||
private[passes] object EnsureNoTargetIO extends firrtl.Transform {
|
||||
def inputForm = HighForm
|
||||
def outputForm = HighForm
|
||||
override def name = "[MIDAS] Ensure No Target IO"
|
||||
|
@ -27,14 +29,20 @@ private[passes] class EnsureNoTargetIO extends firrtl.Transform {
|
|||
val topName = state.circuit.main
|
||||
val topModule = state.circuit.modules.find(_.name == topName).get
|
||||
|
||||
val nonClockPorts = topModule.ports.filter(_.tpe != ClockType)
|
||||
val (clockPorts, nonClockPorts) = topModule.ports.partition(_.tpe == ClockType)
|
||||
|
||||
if (!clockPorts.isEmpty) {
|
||||
val exceptionMessage = "Your target design has the following unexpected clock ports:\n" +
|
||||
clockPorts.map(_.name).mkString("\n") +
|
||||
"\nRemove these ports and generate clocks for your simulated system using a ClockBridge."
|
||||
throw TargetMalformedException(exceptionMessage)
|
||||
}
|
||||
|
||||
if (!nonClockPorts.isEmpty) {
|
||||
val exceptionMessage = """
|
||||
Your target design has dangling IO.
|
||||
You must bind the following top-level ports to an Bridge BlackBox:
|
||||
""" + nonClockPorts.map(_.name).mkString("\n")
|
||||
throw new Exception(exceptionMessage)
|
||||
val exceptionMessage = "Your target design has the following unexpecte IO ports:\n" +
|
||||
nonClockPorts.map(_.name).mkString("\n") +
|
||||
"\nRemove these ports and instead bind their sources/sinks to a target-to-host Bridge."
|
||||
throw TargetMalformedException(exceptionMessage)
|
||||
}
|
||||
state
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
package midas.passes
|
||||
|
||||
import midas.widgets.{BridgeAnnotation}
|
||||
import midas.widgets.{BridgeAnnotation, ClockBridgeAnnotation}
|
||||
import midas.passes.fame.{PromoteSubmodule, PromoteSubmoduleAnnotation, FAMEChannelConnectionAnnotation}
|
||||
|
||||
import firrtl._
|
||||
|
@ -99,6 +99,14 @@ private[passes] class BridgeExtraction extends firrtl.Transform {
|
|||
topModule.foreach(getBridgeConnectivity(portInstPairs, instList))
|
||||
val instMap = instList.toMap
|
||||
|
||||
val clockBridgeInsts = instList.map(inst => inst._1 -> bridgeAnnoMap(instMap(inst._1)))
|
||||
.collect({ case (inst, cb: ClockBridgeAnnotation) => inst })
|
||||
|
||||
val bridgeInstMessage = "You must use a single ClockBridge instance to generate clocks for your simulated system."
|
||||
assert(clockBridgeInsts.nonEmpty, s"No ClockBridge instances found. ${bridgeInstMessage}")
|
||||
assert(clockBridgeInsts.size == 1,
|
||||
s"Multiple ClockBridge instances found: ${clockBridgeInsts.mkString("\n")} ${bridgeInstMessage}")
|
||||
|
||||
val ioAnnotations = portInstPairs.flatMap({ case (port, inst) =>
|
||||
val updatedBridgeAnno = bridgeAnnoMap(instMap(inst)).toIOAnnotation(port)
|
||||
val updatedFCAAnnos = fcaMap(instMap(inst)).map(_.moveFromBridge(port))
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes
|
||||
|
||||
import firrtl._
|
||||
import firrtl.annotations._
|
||||
|
||||
import midas.passes.fame.RTRenamer
|
||||
|
||||
case class FindClockSourceAnnotation(
|
||||
target: ReferenceTarget,
|
||||
originalTarget: Option[ReferenceTarget] = None) extends Annotation {
|
||||
require(target.module == target.circuit, s"Queried leaf clock ${target} must provide an absolute instance path")
|
||||
def update(renames: RenameMap): Seq[FindClockSourceAnnotation] =
|
||||
Seq(this.copy(RTRenamer.exact(renames)(target), originalTarget.orElse(Some(target))))
|
||||
}
|
||||
|
||||
case class ClockSourceAnnotation(queryTarget: ReferenceTarget, source: Option[ReferenceTarget]) extends Annotation {
|
||||
def update(renames: RenameMap): Seq[ClockSourceAnnotation] =
|
||||
Seq(this.copy(queryTarget, source.map(s => RTRenamer.exact(renames)(s))))
|
||||
}
|
||||
|
||||
object FindClockSources extends firrtl.Transform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
val queryAnnotations = state.annotations.collect({ case anno: FindClockSourceAnnotation => anno })
|
||||
val sourceFinder = new ClockSourceFinder(state)
|
||||
val sourceMappings = queryAnnotations.map(qA => qA.target -> sourceFinder.findRootDriver(qA.target)).toMap
|
||||
val clockSourceAnnotations = queryAnnotations.map(qAnno =>
|
||||
ClockSourceAnnotation(qAnno.originalTarget.getOrElse(qAnno.target), sourceMappings(qAnno.target)))
|
||||
val prunedAnnos = state.annotations.filterNot(queryAnnotations.toSet)
|
||||
state.copy(annotations = clockSourceAnnotations ++ prunedAnnos)
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import firrtl.ir._
|
|||
import logger._
|
||||
import firrtl.Mappers._
|
||||
import firrtl.transforms.{DedupModules, DeadCodeElimination}
|
||||
import firrtl.passes.wiring.{WiringTransform}
|
||||
import Utils._
|
||||
|
||||
import java.io.{File, FileWriter}
|
||||
|
@ -44,18 +45,29 @@ private[midas] class MidasTransforms(implicit p: Parameters) extends Transform {
|
|||
firrtl.passes.SplitExpressions,
|
||||
firrtl.passes.CommonSubexpressionElimination,
|
||||
new firrtl.transforms.DeadCodeElimination,
|
||||
new EnsureNoTargetIO,
|
||||
// NB: Carelessly removing this pass will break the FireSim manager as we always
|
||||
// need to generate the *.asserts file. Fix by baking into driver.
|
||||
EnsureNoTargetIO,
|
||||
new BridgeExtraction,
|
||||
new ResolveAndCheck,
|
||||
new EmitFirrtl("post-bridge-extraction.fir"),
|
||||
new HighFirrtlToMiddleFirrtl,
|
||||
new MiddleFirrtlToLowFirrtl,
|
||||
new AutoCounterTransform,
|
||||
new EmitFirrtl("post-autocounter.fir"),
|
||||
new fame.EmitFAMEAnnotations("post-autocounter.json"),
|
||||
new ResolveAndCheck,
|
||||
new AssertPass(dir),
|
||||
new PrintSynthesis(dir),
|
||||
new ResolveAndCheck,
|
||||
new HighFirrtlToMiddleFirrtl,
|
||||
new MiddleFirrtlToLowFirrtl,
|
||||
new BridgeExtraction,
|
||||
new ResolveAndCheck,
|
||||
new MiddleFirrtlToLowFirrtl,
|
||||
new fame.WrapTop,
|
||||
new EmitFirrtl("post-debug-synthesis.fir"),
|
||||
new fame.EmitFAMEAnnotations("post-debug-synthesis.json"),
|
||||
// All trigger sources and sinks must exist in the target RTL before this pass runs
|
||||
TriggerWiring,
|
||||
new EmitFirrtl("post-trigger-wiring.fir"),
|
||||
new fame.EmitFAMEAnnotations("post-trigger-wiring.json"),
|
||||
// We should consider moving these lower
|
||||
ChannelClockInfoAnalysis,
|
||||
UpdateBridgeClockInfo,
|
||||
fame.WrapTop,
|
||||
new ResolveAndCheck,
|
||||
new EmitFirrtl("post-wrap-top.fir")) ++
|
||||
optionalTargetTransforms ++
|
||||
|
@ -66,16 +78,22 @@ private[midas] class MidasTransforms(implicit p: Parameters) extends Transform {
|
|||
new HighFirrtlToMiddleFirrtl,
|
||||
new MiddleFirrtlToLowFirrtl,
|
||||
new fame.FAMEDefaults,
|
||||
new EmitFirrtl("post-fame-defaults.fir"),
|
||||
new fame.EmitFAMEAnnotations("post-fame-defaults.json"),
|
||||
fame.FindDefaultClocks,
|
||||
new fame.EmitFAMEAnnotations("post-find-default-clocks.json"),
|
||||
new fame.ChannelExcision,
|
||||
new fame.InferModelPorts,
|
||||
new ResolveAndCheck,
|
||||
new fame.EmitFAMEAnnotations("post-channel-excision.json"),
|
||||
new EmitFirrtl("post-channel-excision.fir"),
|
||||
new fame.InferModelPorts,
|
||||
new fame.EmitFAMEAnnotations("post-infer-model-ports.json"),
|
||||
new fame.FAMETransform,
|
||||
DefineAbstractClockGate,
|
||||
new EmitFirrtl("post-fame-transform.fir"),
|
||||
new fame.EmitFAMEAnnotations("post-fame-transform.json"),
|
||||
new ResolveAndCheck,
|
||||
new ResolveAndCheck,
|
||||
new fame.EmitAndWrapRAMModels,
|
||||
ConnectHostClock,
|
||||
new EmitFirrtl("post-gen-sram-models.fir"),
|
||||
new ResolveAndCheck) ++
|
||||
Seq(
|
||||
|
|
|
@ -24,16 +24,16 @@ import midas.targetutils.SynthPrintfAnnotation
|
|||
private[passes] class PrintSynthesis(dir: File)(implicit p: Parameters) extends firrtl.Transform {
|
||||
def inputForm = MidForm
|
||||
def outputForm = MidForm
|
||||
override def name = "[MIDAS] Print Synthesis"
|
||||
override def name = "[Golden Gate] Print Synthesis"
|
||||
|
||||
private val printMods = new mutable.HashSet[ModuleTarget]()
|
||||
private val formatStringMap = new mutable.HashMap[ReferenceTarget, String]()
|
||||
val topWiringPrefix = "synthesizedPrintf_"
|
||||
|
||||
// Generates a bundle to aggregate
|
||||
// Generates a bundle containing a print's clock, enable, and argument fields
|
||||
def genPrintBundleType(print: Print): Type = BundleType(Seq(
|
||||
Field("enable", Default, BoolType)) ++
|
||||
print.args.zipWithIndex.map({ case (arg, idx) => Field(s"args_${idx}", Default, arg.tpe) })
|
||||
)
|
||||
print.args.zipWithIndex.map({ case (arg, idx) => Field(s"args_${idx}", Default, arg.tpe) }))
|
||||
|
||||
def getPrintName(p: Print, anno: SynthPrintfAnnotation, ns: Namespace): String = {
|
||||
// If the user provided a name in the annotation use it; otherwise use the source locator
|
||||
|
@ -44,45 +44,31 @@ private[passes] class PrintSynthesis(dir: File)(implicit p: Parameters) extends
|
|||
ns.newName(candidateName)
|
||||
}
|
||||
|
||||
// Hacky: Instead of generated output files, instead sneak out the mappings from the TopWiring
|
||||
// transform.
|
||||
type TopWiringSink = ((ComponentName, Type, Boolean, Seq[String], String), Int)
|
||||
var topLevelOutputs = Seq[TopWiringSink]()
|
||||
def wiringAnnoOutputFunc(td: String,
|
||||
mappings: Seq[TopWiringSink],
|
||||
state: CircuitState): CircuitState = {
|
||||
topLevelOutputs = mappings
|
||||
state
|
||||
}
|
||||
|
||||
// Takes a single printPort and emits an FCCA for each field
|
||||
def genFCCAsFromPort(mT: ModuleTarget, p: Port): Seq[FAMEChannelConnectionAnnotation] = {
|
||||
def genFCCAsFromPort(p: Port, portRT: ReferenceTarget, clockRT: ReferenceTarget): Seq[FAMEChannelConnectionAnnotation] = {
|
||||
p.tpe match {
|
||||
case BundleType(fields) =>
|
||||
fields.map(field =>
|
||||
FAMEChannelConnectionAnnotation(
|
||||
p.name + "_" + field.name,
|
||||
case BundleType(dataFields) =>
|
||||
dataFields.map(field =>
|
||||
FAMEChannelConnectionAnnotation.source(
|
||||
portRT.ref + "_" + field.name,
|
||||
WireChannel,
|
||||
sources = Some(Seq(mT.ref(p.name).field(field.name))),
|
||||
sinks = None
|
||||
clock = Some(clockRT),
|
||||
Seq(portRT.field(field.name))
|
||||
)
|
||||
)
|
||||
case other => Seq()
|
||||
case other => ???
|
||||
}
|
||||
}
|
||||
|
||||
def synthesizePrints(state: CircuitState, printfAnnos: Seq[SynthPrintfAnnotation]): CircuitState = {
|
||||
require(state.annotations.collect({ case t: TopWiringAnnotation => t }).isEmpty,
|
||||
"CircuitState cannot have existing TopWiring annotations before PrintSynthesis.")
|
||||
val c = state.circuit
|
||||
|
||||
def mTarget(m: Module): ModuleTarget = ModuleTarget(c.main, m.name)
|
||||
def portRT(p: Port): ReferenceTarget = ModuleTarget(c.main, c.main).ref(p.name)
|
||||
def portClockRT(p: Port): ReferenceTarget = portRT(p).field("clock")
|
||||
|
||||
val modToAnnos = printfAnnos.groupBy(_.mod)
|
||||
|
||||
val topWiringAnnos = mutable.ArrayBuffer[Annotation](
|
||||
TopWiringOutputFilesAnnotation("unused", wiringAnnoOutputFunc))
|
||||
|
||||
val topWiringPrefix = "synthesizedPrintf_"
|
||||
val topWiringAnnos = mutable.ArrayBuffer[Annotation]()
|
||||
|
||||
def onModule(m: DefModule): DefModule = m match {
|
||||
case m: Module if printMods(mTarget(m)) =>
|
||||
|
@ -92,63 +78,62 @@ private[passes] class PrintSynthesis(dir: File)(implicit p: Parameters) extends
|
|||
|
||||
def onStmt(annos: Seq[SynthPrintfAnnotation], modNamespace: Namespace)
|
||||
(s: Statement): Statement = s.map(onStmt(annos, modNamespace)) match {
|
||||
case p @ Print(_,format,args,_,en) if annos.exists(_.format == format.string) =>
|
||||
case p @ Print(_,format,args,clk ,en) if annos.exists(_.format == format.string) =>
|
||||
val associatedAnno = annos.find(_.format == format.string).get
|
||||
val printName = getPrintName(p, associatedAnno, modNamespace)
|
||||
// Generate an aggregate with all of our arguments; this will be wired out
|
||||
val wire = DefWire(NoInfo, printName, genPrintBundleType(p))
|
||||
val enableConnect = Connect(NoInfo, wsub(WRef(wire), s"enable"), en)
|
||||
val enableConnect = Connect(NoInfo, wsub(WRef(wire), "enable"), en)
|
||||
val argumentConnects = (p.args.zipWithIndex).map({ case (arg, idx) =>
|
||||
Connect(NoInfo,
|
||||
wsub(WRef(wire), s"args_${idx}"),
|
||||
arg)})
|
||||
|
||||
val printBundleTarget = associatedAnno.mod.ref(printName)
|
||||
topWiringAnnos += TopWiringAnnotation(printBundleTarget, topWiringPrefix)
|
||||
val clockTarget = clk match {
|
||||
case WRef(name,_,_,_) => associatedAnno.mod.ref(name)
|
||||
case o => ???
|
||||
}
|
||||
topWiringAnnos += BridgeTopWiringAnnotation(printBundleTarget, clockTarget)
|
||||
formatStringMap(printBundleTarget) = format.serialize
|
||||
Block(Seq(p, wire, enableConnect) ++ argumentConnects)
|
||||
case s => s
|
||||
}
|
||||
|
||||
// Step 1: Find and replace printfs with stubs
|
||||
val processedCircuit = c.map(onModule)
|
||||
val wiredState = (new TopWiringTransform).execute(state.copy(
|
||||
|
||||
// Step 2: Wire out print stubs to top level module
|
||||
val wiredState = (new BridgeTopWiring(topWiringPrefix)).execute(state.copy(
|
||||
circuit = processedCircuit,
|
||||
annotations = state.annotations ++ topWiringAnnos))
|
||||
|
||||
val topModule = wiredState.circuit.modules.find(_.name == wiredState.circuit.main).get
|
||||
val portMap: Map[String, Port] = topModule.ports.map(port => port.name -> port).toMap
|
||||
val addedPrintPorts = topLevelOutputs.map({ case ((cname,_,_,path,prefix),_) =>
|
||||
// Look up the format string by regenerating the referenceTarget to the original print bundle
|
||||
val formatString = formatStringMap(cname)
|
||||
val port = portMap(prefix + path.mkString("_"))
|
||||
(port, formatString)
|
||||
})
|
||||
// Step 3: Group top-wired ports by their associated clock
|
||||
val outputAnnos = wiredState.annotations.collect({ case a: BridgeTopWiringOutputAnnotation => a })
|
||||
val groupedPrints = outputAnnos.groupBy(_.sinkClockPort)
|
||||
|
||||
println(s"[MIDAS] total # of prints synthesized: ${addedPrintPorts.size}")
|
||||
println(s"[Golden Gate] total # of printf instances synthesized: ${outputAnnos.size}")
|
||||
|
||||
val printRecordAnno = addedPrintPorts match {
|
||||
case Nil => Seq()
|
||||
case ports => {
|
||||
// TODO: Generate sensible channel annotations once we can aggregate wire channels
|
||||
val portName = topWiringPrefix.stripSuffix("_")
|
||||
val mT = ModuleTarget(c.main, c.main)
|
||||
val portRT = mT.ref(portName)
|
||||
// Step 4: Generate FCCAs and Bridge Annotations for each clock domain
|
||||
val topModule = wiredState.circuit.modules.find(_.name == c.main).get
|
||||
val portMap = topModule.ports.map(p => portRT(p) -> p).toMap
|
||||
|
||||
val fccaAnnos = ports.flatMap({ case (port, _) => genFCCAsFromPort(mT, port) })
|
||||
val bridgeAnno = BridgeIOAnnotation(
|
||||
target = portRT,
|
||||
widget = (p: Parameters) => new PrintBridgeModule(addedPrintPorts)(p),
|
||||
channelNames = fccaAnnos.map(_.globalName)
|
||||
)
|
||||
bridgeAnno +: fccaAnnos
|
||||
}
|
||||
val printRecordAnnos = for ((clockRT, oAnnos) <- groupedPrints.toSeq.sortBy(_._1.ref)) yield {
|
||||
val fccaAnnos = oAnnos.flatMap({ case BridgeTopWiringOutputAnnotation(_,_,oPortRT,_,oClockRT) =>
|
||||
genFCCAsFromPort(portMap(oPortRT), oPortRT, oClockRT) })
|
||||
|
||||
val portTuples = oAnnos.map({ case BridgeTopWiringOutputAnnotation(srcRT,_,oPortRT,_,_) =>
|
||||
portMap(oPortRT) -> formatStringMap(srcRT) })
|
||||
|
||||
val bridgeAnno = BridgeIOAnnotation(
|
||||
target = ModuleTarget(c.main, c.main).ref(topWiringPrefix.stripSuffix("_")),
|
||||
widget = (p: Parameters) => new PrintBridgeModule(portTuples)(p),
|
||||
channelNames = fccaAnnos.map(_.globalName)
|
||||
)
|
||||
bridgeAnno +: fccaAnnos
|
||||
}
|
||||
// Remove added TopWiringAnnotations to prevent being reconsumed by a downstream pass
|
||||
val cleanedAnnotations = wiredState.annotations.flatMap({
|
||||
case TopWiringAnnotation(_,_) => None
|
||||
case otherAnno => Some(otherAnno)
|
||||
})
|
||||
wiredState.copy(annotations = cleanedAnnotations ++ printRecordAnno)
|
||||
// Remove added Annotations to prevent being reconsumed by a downstream pass
|
||||
val cleanedAnnotations = wiredState.annotations.filterNot(outputAnnos.toSet)
|
||||
wiredState.copy(annotations = cleanedAnnotations ++ printRecordAnnos.toSeq.flatten)
|
||||
}
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
|
|
|
@ -14,7 +14,7 @@ import firrtl.Mappers._
|
|||
import firrtl.passes.LowerTypes.loweredName
|
||||
import firrtl.Utils.{BoolType, splitRef, mergeRef, create_exps, gender, module_type}
|
||||
import firrtl.passes.wiring._
|
||||
import fame.{FAMEChannelConnectionAnnotation, FAMEChannelAnalysis, FAME1Transform}
|
||||
import fame.{FAMEChannelConnectionAnnotation, FAMEChannelPortsAnnotation, FAMEChannelAnalysis, FAME1Transform}
|
||||
import Utils._
|
||||
import freechips.rocketchip.config.Parameters
|
||||
|
||||
|
@ -128,7 +128,6 @@ private[passes] class SimulationMapping(targetName: String)(implicit val p: Para
|
|||
|
||||
val transforms = Seq(
|
||||
new Fame1Instances,
|
||||
new WiringTransform,
|
||||
new PreLinkRenaming(Namespace(innerCircuit)))
|
||||
val outerState = new LowFirrtlCompiler().compile(CircuitState(chirrtl, ChirrtlForm, annos), transforms)
|
||||
|
||||
|
@ -144,7 +143,7 @@ private[passes] class SimulationMapping(targetName: String)(implicit val p: Para
|
|||
|
||||
// FIXME: Renamer complains if i leave these in
|
||||
val innerAnnos = loweredInnerState.annotations.filter(_ match {
|
||||
case _: FAMEChannelConnectionAnnotation => false
|
||||
case _: FAMEChannelConnectionAnnotation | _: FAMEChannelPortsAnnotation => false
|
||||
case _: BridgeIOAnnotation => false
|
||||
case _ => true
|
||||
})
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes
|
||||
|
||||
import midas.passes.fame.{FAMEChannelConnectionAnnotation, TargetClockChannel}
|
||||
import midas.widgets.{RationalClock}
|
||||
|
||||
import firrtl._
|
||||
import firrtl.annotations._
|
||||
|
||||
|
||||
/**
|
||||
* [[ChannelClockInfoAnalysis]]'s output annotation. Maps channel global name
|
||||
* (See [[FAMEChannelConnectionAnnotation]] to a clock info class.
|
||||
*/
|
||||
case class ChannelClockInfoAnnotation(infoMap: Map[String, RationalClock]) extends NoTargetAnnotation
|
||||
|
||||
/**
|
||||
* Returns a map from a channel's global name to a RationalClock case class which
|
||||
* contains metadata about the target clock including its name and relative
|
||||
* frequency to the base clock
|
||||
*
|
||||
*/
|
||||
object ChannelClockInfoAnalysis extends Transform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
def analyze(state: CircuitState): Map[String, RationalClock] = {
|
||||
val clockChannels = state.annotations.collect {
|
||||
case FAMEChannelConnectionAnnotation(_,TargetClockChannel(clocks),_,_,Some(clockRTs)) =>
|
||||
clockRTs zip clocks
|
||||
}
|
||||
require(clockChannels.size == 1,
|
||||
s"Expected exactly one clock channel annotation. Got: ${clockChannels.size}")
|
||||
val sourceInfoMap = clockChannels.head.toMap
|
||||
|
||||
// This relies on the assumption that the clock channel will not have its clock field set
|
||||
val channelClocks = state.annotations.collect({
|
||||
case FAMEChannelConnectionAnnotation(name,_,Some(clock),_,_) => (name, clock)
|
||||
}).toMap
|
||||
|
||||
val finder = new ClockSourceFinder(state)
|
||||
val clockSourceMap = channelClocks.map({ case (k, v) => v -> finder.findRootDriver(v) }).toMap
|
||||
channelClocks.mapValues(sinkClock => sourceInfoMap(clockSourceMap(sinkClock).get))
|
||||
}
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
val infoAnno = ChannelClockInfoAnnotation(analyze(state))
|
||||
state.copy(annotations = infoAnno +: state.annotations)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
//See LICENSE for license details.
|
||||
|
||||
package midas.passes
|
||||
|
||||
import midas.targetutils.{TriggerSourceAnnotation, TriggerSinkAnnotation}
|
||||
import midas.passes.fame._
|
||||
|
||||
import freechips.rocketchip.util.DensePrefixSum
|
||||
import firrtl._
|
||||
import firrtl.annotations._
|
||||
import firrtl.ir._
|
||||
import firrtl.Mappers._
|
||||
import firrtl.passes.wiring.{SinkAnnotation, SourceAnnotation, WiringTransform}
|
||||
import firrtl.Utils.{zero, BoolType}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/*
|
||||
* Implements Golden Gate's trigger system by emitting target-hardware to aggregate
|
||||
* all trigger source signals into trigger enables.
|
||||
*
|
||||
* Refer to the FireSim Docs for more detail, and for a schematic of the generated HW.
|
||||
*/
|
||||
private[passes] object TriggerWiring extends firrtl.Transform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = HighForm
|
||||
override def name = "[Golden Gate] Trigger Wiring"
|
||||
val topWiringPrefix = "simulationTrigger_"
|
||||
val sinkWiringKey = "trigger_sink"
|
||||
|
||||
// Defines the width of credit and debit counters local to a specific clock domain
|
||||
// For the trigger to function correctly:
|
||||
// localWidth >= log2Ceil(max(localCreditSources, localDebitSources) * Ceil(N/M))
|
||||
// where:
|
||||
// local{Credit,Debit}Sources are the number of the associated source in the local clock domain,
|
||||
// the local clock frequency is (N/M) times the base frequency
|
||||
val localCType = UIntType(IntWidth(16))
|
||||
// Defines the type of the global counter in the base clock domain. This
|
||||
// just needs to be large enough to represent the largest number of credits
|
||||
// the system will produce. Since there are only two of these, make it large
|
||||
// to be safe.
|
||||
val globalCType = UIntType(IntWidth(32))
|
||||
|
||||
// Masks off trigger sources when the are under reset.
|
||||
private def gateEventsWithReset(sourceModuleMap: Map[String, Seq[TriggerSourceAnnotation]],
|
||||
updatedAnnos: mutable.ArrayBuffer[TriggerSourceAnnotation])
|
||||
(mod: DefModule): DefModule = mod match {
|
||||
case m: Module if sourceModuleMap.isDefinedAt(m.name) =>
|
||||
val annos = sourceModuleMap(m.name)
|
||||
val mT = annos.head.enclosingModuleTarget
|
||||
val moduleNS = Namespace(mod)
|
||||
val addedStmts = annos.flatMap({ anno =>
|
||||
if (anno.reset.nonEmpty) {
|
||||
val eventName = moduleNS.newName(anno.target.ref + "_masked")
|
||||
updatedAnnos += anno.copy(target = mT.ref(eventName))
|
||||
Seq(DefNode(NoInfo, eventName, And(Negate(WRef(anno.reset.get.ref)), WRef(anno.target.ref))))
|
||||
} else {
|
||||
updatedAnnos += anno
|
||||
Nil
|
||||
}
|
||||
})
|
||||
m.copy(body = Block(m.body, addedStmts:_*))
|
||||
case o => o
|
||||
}
|
||||
|
||||
// Generates the sink-side hardware. See onStmtSink
|
||||
private def onModuleSink(sinkAnnoModuleMap: Map[String, Seq[TriggerSinkAnnotation]],
|
||||
addedAnnos: mutable.ArrayBuffer[Annotation])
|
||||
(m: DefModule): DefModule = m match {
|
||||
case m: Module if sinkAnnoModuleMap.isDefinedAt(m.name) =>
|
||||
val sinkNameMap = sinkAnnoModuleMap(m.name).map(anno => anno.target.ref -> anno).toMap
|
||||
val ns = Namespace(m)
|
||||
m.map(onStmtSink(sinkNameMap, addedAnnos, ns))
|
||||
case o => o
|
||||
}
|
||||
|
||||
/**
|
||||
* For each TriggerSink:
|
||||
* 1) Emit a register that will synchronize the trigger signal to the to local domain (from the base one)
|
||||
* 2) Emit a wiring annotation pointing at that register.
|
||||
*/
|
||||
private def onStmtSink(sinkAnnos: Map[String, TriggerSinkAnnotation],
|
||||
addedAnnos: mutable.ArrayBuffer[Annotation],
|
||||
ns: Namespace)
|
||||
(s: Statement): Statement = s.map(onStmtSink(sinkAnnos, addedAnnos, ns)) match {
|
||||
case node@DefNode(_,name,_) if sinkAnnos.isDefinedAt(name) =>
|
||||
val sinkAnno = sinkAnnos(name)
|
||||
val mT = sinkAnno.enclosingModuleTarget
|
||||
val triggerSyncName = ns.newName("trigger_sync")
|
||||
val triggerSync = RegZeroPreset(NoInfo, triggerSyncName, BoolType, WRef(sinkAnno.clock.ref))
|
||||
addedAnnos += SinkAnnotation(mT.ref(triggerSyncName).toNamed, sinkWiringKey)
|
||||
Block(triggerSync, node.copy(value = WRef(triggerSync)))
|
||||
// Implement the missing cases?
|
||||
case r@DefRegister(_,name,_,_,_,_) if sinkAnnos.isDefinedAt(name) => ???
|
||||
case s => s
|
||||
}
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
|
||||
val topModName = state.circuit.main
|
||||
val topMod = state.circuit.modules.find(_.name == topModName).get
|
||||
val prexistingPorts = topMod.ports
|
||||
// 1) Collect Trigger Annotations, and generate BridgeTopWiring annotations
|
||||
val srcCreditAnnos = new mutable.ArrayBuffer[TriggerSourceAnnotation]()
|
||||
val srcDebitAnnos = new mutable.ArrayBuffer[TriggerSourceAnnotation]()
|
||||
val sinkAnnos = new mutable.ArrayBuffer[TriggerSinkAnnotation]()
|
||||
state.annotations.collect({
|
||||
case a: TriggerSourceAnnotation if a.sourceType => srcCreditAnnos += a
|
||||
case a: TriggerSourceAnnotation => srcDebitAnnos += a
|
||||
case a: TriggerSinkAnnotation => sinkAnnos += a
|
||||
})
|
||||
|
||||
require(!(srcCreditAnnos.isEmpty && srcDebitAnnos.nonEmpty), "Provided trigger debit sources but no credit sources")
|
||||
// It may make sense to relax this in the future
|
||||
// This would enable trigger without posibility of disabling it in the future
|
||||
require(!(srcDebitAnnos.isEmpty && srcCreditAnnos.nonEmpty), "Provided trigger credit sources but no debit sources")
|
||||
|
||||
val updatedState = if ((srcCreditAnnos.isEmpty && srcDebitAnnos.isEmpty) || sinkAnnos.isEmpty) {
|
||||
state
|
||||
} else {
|
||||
// Step 1) Gate credits and debits with their associated reset, if provided
|
||||
val updatedAnnos = new mutable.ArrayBuffer[TriggerSourceAnnotation]()
|
||||
val srcAnnoMap = (srcCreditAnnos ++ srcDebitAnnos).groupBy(_.enclosingModule)
|
||||
val gatedCircuit = state.circuit.map(gateEventsWithReset(srcAnnoMap, updatedAnnos))
|
||||
val (gatedCredits, gatedDebits) = updatedAnnos.partition(_.sourceType)
|
||||
|
||||
// Step 2) Use bridge topWiring to generate inter-module connectivity -- but drop the port list
|
||||
val bridgeTopWiringAnnos = updatedAnnos.map(anno => BridgeTopWiringAnnotation(anno.target, anno.clock))
|
||||
val wiredState = (new BridgeTopWiring(topWiringPrefix)).execute(state.copy(
|
||||
circuit = gatedCircuit, annotations = state.annotations ++ bridgeTopWiringAnnos))
|
||||
val wiredTopModule = wiredState.circuit.modules.collectFirst({
|
||||
case m@Module(_,name,_,_) if name == topModName => m
|
||||
}).get
|
||||
val otherModules = wiredState.circuit.modules.filter(_.name != topModName)
|
||||
val addedPorts = wiredTopModule.ports.filterNot(p => prexistingPorts.contains(p))
|
||||
|
||||
// Step 3: Group top-wired outputs by their associated clock
|
||||
val outputAnnos = wiredState.annotations.collect({ case a: BridgeTopWiringOutputAnnotation => a })
|
||||
val groupedTriggers = outputAnnos.groupBy(_.srcClockPort)
|
||||
|
||||
// Step 4: Convert port assignments to wire assignments
|
||||
val portName2WireMap = addedPorts.map(p => p.name -> DefWire(NoInfo, p.name, p.tpe)).toMap
|
||||
def updateAssignments(stmt: Statement): Statement = stmt.map(updateAssignments) match {
|
||||
case c@Connect(_, WRef(name,_,_,_), _) if portName2WireMap.isDefinedAt(name) =>
|
||||
val defWire = portName2WireMap(name)
|
||||
Block(defWire, c.copy(loc = WRef(defWire)))
|
||||
case o => o
|
||||
}
|
||||
|
||||
val portRemovedBody = wiredTopModule.body.map(updateAssignments)
|
||||
|
||||
// 5) Per-clock-domain: count local credits and debits
|
||||
val ns = Namespace(wiredTopModule)
|
||||
val addedStmts = new mutable.ArrayBuffer[Statement]()
|
||||
|
||||
def addReduce(bools: Seq[WRef]): WRef = DensePrefixSum(bools)({ case (a, b) =>
|
||||
val name = ns.newTemp
|
||||
val node = DefNode(NoInfo, name, DoPrim(PrimOps.Add, Seq(a, b), Seq.empty, UnknownType))
|
||||
addedStmts += node
|
||||
WRef(node)
|
||||
}).last
|
||||
|
||||
def counter(name: String, tpe: UIntType, clock: WRef, incr: WRef): (DefRegister, DefNode) = {
|
||||
val countName = ns.newName(name)
|
||||
val count = RegZeroPreset(NoInfo, countName, tpe, clock)
|
||||
val nextName = ns.newName(countName + "_next")
|
||||
val next = DefNode(NoInfo, nextName, DoPrim(PrimOps.Add, Seq(WRef(count), incr), Seq.empty, tpe))
|
||||
val countUpdate = Connect(NoInfo, WRef(count), WRef(next))
|
||||
addedStmts ++= Seq(count, next, countUpdate)
|
||||
(count, next)
|
||||
}
|
||||
|
||||
// Add-reduces a set of UInt signals, before adding it to a running count,
|
||||
// returning a reference to value the counter will take on in the next cycle.
|
||||
def doAccounting(counterType: UIntType, clock: WRef)(name: String, bools: Seq[WRef]): WRef =
|
||||
WRef(counter(name, counterType, clock, addReduce(bools))._2)
|
||||
|
||||
// In each clock domain, add up all of the credits and debits
|
||||
val (localCredits, localDebits) = (for ((clockRT, oAnnos) <- groupedTriggers) yield {
|
||||
val credits = oAnnos.collect {
|
||||
case a if gatedCredits.exists(_.target == a.pathlessSource) => WRef(portName2WireMap(a.topSink.ref))
|
||||
}
|
||||
val debits = oAnnos.collect {
|
||||
case a if gatedDebits.exists(_.target == a.pathlessSource) => WRef(portName2WireMap(a.topSink.ref))
|
||||
}
|
||||
def doLocalAccounting = doAccounting(localCType, WRef(clockRT.ref)) _
|
||||
|
||||
val domainName = clockRT.ref
|
||||
(doLocalAccounting(s"${domainName}_credits", credits), doLocalAccounting(s"${domainName}_debits", debits))
|
||||
}).unzip
|
||||
|
||||
// Step 6) Synchronize and aggregate local counts into global counts in the base clock domain
|
||||
val refClockRT = wiredState.annotations.collectFirst({
|
||||
case FAMEChannelConnectionAnnotation(_,TargetClockChannel(_),_,_,Some(clock :: _)) => clock
|
||||
}).get
|
||||
|
||||
// We only need to use a single register to synchronize a signal in GG, we use two here
|
||||
// to measure how much the local count has changed between base clock cycles (hence, Diff).
|
||||
def syncAndDiff(next: WRef): WRef = {
|
||||
val name = next.name
|
||||
val syncNameS1 = ns.newName(s"${name}_count_sync_s1")
|
||||
val syncS1 = RegZeroPreset(NoInfo, syncNameS1, localCType, WRef(refClockRT.ref))
|
||||
val syncNameS2 = ns.newName(s"${name}_count_sync_s2")
|
||||
val syncS2 = RegZeroPreset(NoInfo, syncNameS2, localCType, WRef(refClockRT.ref))
|
||||
val diffName = ns.newName(s"${name}_diff")
|
||||
val diffNode = DefNode(NoInfo, diffName, DoPrim(PrimOps.Sub, Seq(WRef(syncS1), WRef(syncS2)), Seq.empty, localCType))
|
||||
addedStmts ++= Seq(
|
||||
syncS1, syncS2, diffNode,
|
||||
Connect(NoInfo, WRef(syncS1), next),
|
||||
Connect(NoInfo, WRef(syncS2), WRef(syncS1))
|
||||
)
|
||||
WRef(diffNode)
|
||||
}
|
||||
val creditUpdates = localCredits.map(syncAndDiff).toSeq
|
||||
val debitUpdates = localDebits.map(syncAndDiff).toSeq
|
||||
|
||||
// Add together the changes in local debits and credits, and apply them
|
||||
// to the global count
|
||||
def doGlobalAccounting = doAccounting(globalCType, WRef(refClockRT.ref)) _
|
||||
val totalCredit = doGlobalAccounting("totalCredits", creditUpdates)
|
||||
val totalDebit = doGlobalAccounting("totalDebits", debitUpdates)
|
||||
|
||||
// Step 7) Generate the trigger enable, and prep all sinks for Wiring by
|
||||
// adding a synchronization register
|
||||
val triggerName = ns.newName("trigger_source")
|
||||
val triggerSource = DefNode(NoInfo, triggerName, Neq(totalCredit, totalDebit))
|
||||
val triggerSourceRT = ModuleTarget(topModName, topModName).ref(triggerName)
|
||||
addedStmts += triggerSource
|
||||
val topModWithTrigger = wiredTopModule.copy(ports = prexistingPorts, body = Block(portRemovedBody, addedStmts:_*))
|
||||
val updatedCircuit = wiredState.circuit.copy(modules = topModWithTrigger +: otherModules)
|
||||
|
||||
// Step 8) Wire the generated trigger to all sinks using the WiringTranform
|
||||
val sinkModuleMap = sinkAnnos.groupBy(_.target.module)
|
||||
val wiringAnnos = new mutable.ArrayBuffer[Annotation]
|
||||
wiringAnnos += SourceAnnotation(triggerSourceRT.toNamed, sinkWiringKey)
|
||||
val preSinkWiringCircuit = updatedCircuit.map(onModuleSink(sinkModuleMap, wiringAnnos))
|
||||
val preSinkWiringState = CircuitState(preSinkWiringCircuit, HighForm, wiredState.annotations ++ wiringAnnos)
|
||||
|
||||
val sinkWiringTransforms = Seq(
|
||||
new ResolveAndCheck,
|
||||
new HighFirrtlToMiddleFirrtl,
|
||||
new WiringTransform,
|
||||
new ResolveAndCheck)
|
||||
sinkWiringTransforms.foldLeft(preSinkWiringState)((in, xform) => xform.runTransform(in))
|
||||
}
|
||||
|
||||
val cleanedAnnos = updatedState.annotations.flatMap({
|
||||
case a: TriggerSourceAnnotation => None
|
||||
case a: TriggerSinkAnnotation => None
|
||||
case a: BridgeTopWiringOutputAnnotation => None
|
||||
case o => Some(o)
|
||||
})
|
||||
updatedState.copy(annotations = cleanedAnnos)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes
|
||||
|
||||
import midas.widgets.{RationalClock, BridgeIOAnnotation}
|
||||
|
||||
import firrtl._
|
||||
import firrtl.annotations._
|
||||
|
||||
/**
|
||||
* Determines which clock each bridge is synchronous with, and updates that bridge's IO annotation
|
||||
* to include it's domain clock info.
|
||||
*
|
||||
*/
|
||||
object UpdateBridgeClockInfo extends Transform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
val infoMaps = state.annotations.collect {
|
||||
case ChannelClockInfoAnnotation(map) => map
|
||||
}
|
||||
require(infoMaps.size == 1,
|
||||
s"Expected exactly one ChannelClockInfoAnnotation. Got: ${infoMaps.size}")
|
||||
val infoMap = infoMaps.head
|
||||
val annosx = state.annotations.map({
|
||||
case a@BridgeIOAnnotation(_,_,None,_,_,_) =>
|
||||
// There will be some cases where this is left unpopulated, i.e., for the clockBridge
|
||||
a.copy(clockInfo = infoMap.get(a.channelMapping.values.head))
|
||||
case o => o
|
||||
})
|
||||
state.copy(annotations = annosx)
|
||||
}
|
||||
}
|
|
@ -3,22 +3,25 @@ package midas.passes.fame
|
|||
import firrtl._
|
||||
import annotations._
|
||||
|
||||
import midas.targetutils.FAMEAnnotation
|
||||
import midas.widgets.RationalClock
|
||||
|
||||
/**
|
||||
* An annotation that describes the ports that constitute one channel
|
||||
* from the perspective of a particular module that will be replaced
|
||||
* by a simulation model. Note that this describes the channels as
|
||||
* they appear locally from within the module, so this annotation will
|
||||
* apply to *all* instances of that module.
|
||||
*
|
||||
*
|
||||
* Upon creation, this annotation is associated with a particular
|
||||
* target RTL module M that will eventually be transformed into a FAME
|
||||
* model. This module must only be instantiated at the top level.
|
||||
*
|
||||
*
|
||||
* @param localName refers to the name of the channel within the scope of the
|
||||
* eventual FAME model. This will be used as the channel’s port
|
||||
* name in the model. It will also be used to identify
|
||||
* microarchitectural state associated with the channel
|
||||
*
|
||||
*
|
||||
* @param ports a list of the ports that are grouped into the channel. The
|
||||
* ReferenceTargets should be rooted at M, since this information is
|
||||
* local to the module. This is also what associates the annotation
|
||||
|
@ -26,12 +29,13 @@ import annotations._
|
|||
*/
|
||||
case class FAMEChannelPortsAnnotation(
|
||||
localName: String,
|
||||
ports: Seq[ReferenceTarget]) extends Annotation {
|
||||
clockPort: Option[ReferenceTarget],
|
||||
ports: Seq[ReferenceTarget]) extends Annotation with FAMEAnnotation {
|
||||
def update(renames: RenameMap): Seq[Annotation] = {
|
||||
val renamer = RTRenamer.exact(renames)
|
||||
Seq(FAMEChannelPortsAnnotation(localName, ports.map(renamer)))
|
||||
Seq(FAMEChannelPortsAnnotation(localName, clockPort.map(renamer), ports.map(renamer)))
|
||||
}
|
||||
override def getTargets: Seq[ReferenceTarget] = ports
|
||||
override def getTargets: Seq[ReferenceTarget] = clockPort ++: ports
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,15 +46,20 @@ case class FAMEChannelPortsAnnotation(
|
|||
*
|
||||
* @param channelInfo describes the type of the channel (Wire, Forward/Reverse
|
||||
* Decoupled)
|
||||
*
|
||||
* @param clock the *source* of the clock (if any) associated with this channel
|
||||
*
|
||||
* @note The clock source must be a port on the model side of the channel
|
||||
*/
|
||||
case class FAMEChannelConnectionAnnotation(
|
||||
globalName: String,
|
||||
channelInfo: FAMEChannelInfo,
|
||||
clock: Option[ReferenceTarget],
|
||||
sources: Option[Seq[ReferenceTarget]],
|
||||
sinks: Option[Seq[ReferenceTarget]]) extends Annotation with HasSerializationHints {
|
||||
sinks: Option[Seq[ReferenceTarget]]) extends Annotation with FAMEAnnotation with HasSerializationHints {
|
||||
def update(renames: RenameMap): Seq[Annotation] = {
|
||||
val renamer = RTRenamer.exact(renames)
|
||||
Seq(FAMEChannelConnectionAnnotation(globalName, channelInfo.update(renames), sources.map(_.map(renamer)), sinks.map(_.map(renamer))))
|
||||
Seq(FAMEChannelConnectionAnnotation(globalName, channelInfo.update(renames), clock.map(renamer), sources.map(_.map(renamer)), sinks.map(_.map(renamer))))
|
||||
}
|
||||
def typeHints(): Seq[Class[_]] = Seq(channelInfo.getClass)
|
||||
|
||||
|
@ -60,7 +69,7 @@ case class FAMEChannelConnectionAnnotation(
|
|||
def updateRT(rT: ReferenceTarget): ReferenceTarget = ModuleTarget(rT.circuit, rT.circuit).ref(portName).field(rT.ref)
|
||||
|
||||
require(sources == None || sinks == None, "Bridge-connected channels cannot loopback")
|
||||
val rTs = sources.getOrElse(sinks.get) ++ (channelInfo match {
|
||||
val rTs = sources.getOrElse(sinks.get) ++ clock ++ (channelInfo match {
|
||||
case i: DecoupledForwardChannel => Seq(i.readySink.getOrElse(i.readySource.get))
|
||||
case other => Seq()
|
||||
})
|
||||
|
@ -69,22 +78,41 @@ case class FAMEChannelConnectionAnnotation(
|
|||
copy(globalName = s"${portName}_${globalName}").update(localRenames).head.asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
override def getTargets: Seq[ReferenceTarget] = sources.toSeq.flatten ++ sinks.toSeq.flatten
|
||||
override def getTargets: Seq[ReferenceTarget] = clock ++: (sources.toSeq.flatten ++ sinks.toSeq.flatten)
|
||||
}
|
||||
|
||||
// Helper factory methods for generating bridge annotations that have only sinks or sources
|
||||
// Helper factory methods for generating common patterns
|
||||
object FAMEChannelConnectionAnnotation {
|
||||
def implicitlyClockedLoopback(
|
||||
globalName: String,
|
||||
channelInfo: FAMEChannelInfo,
|
||||
sources: Seq[ReferenceTarget],
|
||||
sinks: Seq[ReferenceTarget]): FAMEChannelConnectionAnnotation =
|
||||
FAMEChannelConnectionAnnotation(globalName, channelInfo, None, Some(sources), Some(sinks))
|
||||
|
||||
def sink(
|
||||
globalName: String,
|
||||
channelInfo: FAMEChannelInfo,
|
||||
clock: Option[ReferenceTarget],
|
||||
sinks: Seq[ReferenceTarget]): FAMEChannelConnectionAnnotation =
|
||||
FAMEChannelConnectionAnnotation(globalName, channelInfo, None, Some(sinks))
|
||||
FAMEChannelConnectionAnnotation(globalName, channelInfo, clock, None, Some(sinks))
|
||||
|
||||
def implicitlyClockedSink(
|
||||
globalName: String,
|
||||
channelInfo: FAMEChannelInfo,
|
||||
sinks: Seq[ReferenceTarget]): FAMEChannelConnectionAnnotation = sink(globalName, channelInfo, None, sinks)
|
||||
|
||||
def source(
|
||||
globalName: String,
|
||||
channelInfo: FAMEChannelInfo,
|
||||
clock: Option[ReferenceTarget],
|
||||
sources: Seq[ReferenceTarget]): FAMEChannelConnectionAnnotation =
|
||||
FAMEChannelConnectionAnnotation(globalName, channelInfo, Some(sources), None)
|
||||
FAMEChannelConnectionAnnotation(globalName, channelInfo, clock, Some(sources), None)
|
||||
|
||||
def implicitlyClockedSource(
|
||||
globalName: String,
|
||||
channelInfo: FAMEChannelInfo,
|
||||
sources: Seq[ReferenceTarget]): FAMEChannelConnectionAnnotation = source(globalName, channelInfo, None, sources)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,7 +132,7 @@ sealed trait FAMEChannelInfo {
|
|||
*/
|
||||
case class PipeChannel(val latency: Int) extends FAMEChannelInfo
|
||||
|
||||
/**
|
||||
/**
|
||||
* Indicates that a channel connection is the reverse (ready) half of
|
||||
* a decoupled target connection. Since the forward half incorporates
|
||||
* references to the ready signals, this channel contains no signal
|
||||
|
@ -113,17 +141,22 @@ case class PipeChannel(val latency: Int) extends FAMEChannelInfo
|
|||
case object DecoupledReverseChannel extends FAMEChannelInfo
|
||||
|
||||
/**
|
||||
* Indicates that a channel connection is the reverse (ready) half of
|
||||
* Indicates that a channel connection carries target clocks
|
||||
*/
|
||||
case class TargetClockChannel(clockInfo: Seq[RationalClock]) extends FAMEChannelInfo
|
||||
|
||||
/**
|
||||
* Indicates that a channel connection is the forward (valid) half of
|
||||
* a decoupled target connection.
|
||||
*
|
||||
*
|
||||
* @param readySink sink port component of the corresponding reverse channel
|
||||
*
|
||||
*
|
||||
* @param validSource valid port component from this channel's sources
|
||||
*
|
||||
*
|
||||
* @param readySource source port component of the corresponding reverse channel
|
||||
*
|
||||
*
|
||||
* @param validSink valid port component from this channel's sinks
|
||||
*
|
||||
*
|
||||
* @note (readySink, validSource) are on one model, (readySource, validSink) on the other
|
||||
*/
|
||||
case class DecoupledForwardChannel(
|
||||
|
@ -150,15 +183,6 @@ object DecoupledForwardChannel {
|
|||
DecoupledForwardChannel(Some(ready), Some(valid), None, None)
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a particular instance is a FAME Model
|
||||
*/
|
||||
case class FAMEModelAnnotation(target: InstanceTarget) extends SingleTargetAnnotation[InstanceTarget] {
|
||||
def targets = Seq(target)
|
||||
def duplicate(n: InstanceTarget) = this.copy(n)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Specifies what form of FAME transform should be applied when
|
||||
* generated a simulation model from a target module.
|
||||
|
@ -184,7 +208,9 @@ case object FAME1Transform extends FAMETransformType
|
|||
* this is a ModuleTarget, all instances at the top level will be
|
||||
* transformed identically.
|
||||
*/
|
||||
case class FAMETransformAnnotation(transformType: FAMETransformType, target: ModuleTarget) extends SingleTargetAnnotation[ModuleTarget] {
|
||||
case class FAMETransformAnnotation(
|
||||
transformType: FAMETransformType,
|
||||
target: ModuleTarget) extends SingleTargetAnnotation[ModuleTarget] with FAMEAnnotation {
|
||||
def targets = Seq(target)
|
||||
def duplicate(n: ModuleTarget) = this.copy(transformType, n)
|
||||
}
|
||||
|
@ -194,17 +220,18 @@ case class FAMETransformAnnotation(transformType: FAMETransformType, target: Mod
|
|||
* level in the hierarchy. The specified instance will be pulled out
|
||||
* of its parent module and will reside in its "grandparent" module
|
||||
* after the PromoteSubmodule transform has run.
|
||||
*
|
||||
*
|
||||
* @param target The instance to be promoted. Note that this must
|
||||
* be a *local* instance target, as all instances of the parent
|
||||
* module will be transformed identically.
|
||||
*/
|
||||
case class PromoteSubmoduleAnnotation(target: InstanceTarget) extends SingleTargetAnnotation[InstanceTarget] {
|
||||
case class PromoteSubmoduleAnnotation(
|
||||
target: InstanceTarget) extends SingleTargetAnnotation[InstanceTarget] with FAMEAnnotation {
|
||||
def targets = Seq(target)
|
||||
def duplicate(n: InstanceTarget) = this.copy(n)
|
||||
}
|
||||
|
||||
abstract class FAMEGlobalSignal extends SingleTargetAnnotation[ReferenceTarget] {
|
||||
abstract class FAMEGlobalSignal extends SingleTargetAnnotation[ReferenceTarget] with FAMEAnnotation {
|
||||
val target: ReferenceTarget
|
||||
def targets = Seq(target)
|
||||
def duplicate(n: ReferenceTarget): FAMEGlobalSignal
|
||||
|
@ -218,7 +245,7 @@ case class FAMEHostReset(target: ReferenceTarget) extends FAMEGlobalSignal {
|
|||
def duplicate(t: ReferenceTarget): FAMEHostReset = this.copy(t)
|
||||
}
|
||||
|
||||
abstract class MemPortAnnotation extends Annotation {
|
||||
abstract class MemPortAnnotation extends Annotation with FAMEAnnotation {
|
||||
val en: ReferenceTarget
|
||||
val addr: ReferenceTarget
|
||||
}
|
||||
|
@ -287,3 +314,24 @@ case class ModelReadWritePort(
|
|||
}
|
||||
override def getTargets: Seq[ReferenceTarget] = Seq(wmode, rdata, wdata, wmask, addr, en)
|
||||
}
|
||||
|
||||
/**
|
||||
* A pass that dumps all FAME annotations to a file for debugging.
|
||||
*/
|
||||
class EmitFAMEAnnotations(fileName: String) extends firrtl.Transform {
|
||||
import firrtl.options.TargetDirAnnotation
|
||||
def inputForm = UnknownForm
|
||||
def outputForm = UnknownForm
|
||||
|
||||
override def name = s"[Golden Gate] Debugging FAME Annotation Emission Pass: $fileName"
|
||||
|
||||
def execute(state: CircuitState) = {
|
||||
val targetDir = state.annotations.collectFirst { case TargetDirAnnotation(dir) => dir }
|
||||
val dirName = targetDir.getOrElse(".")
|
||||
val outputFile = new java.io.PrintWriter(s"${dirName}/${fileName}")
|
||||
val fameAnnos = state.annotations.collect { case fa: FAMEAnnotation => fa }
|
||||
outputFile.write(JsonProtocol.serialize(fameAnnos))
|
||||
outputFile.close()
|
||||
state
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,15 @@ import Utils._
|
|||
import firrtl.passes.MemPortUtils
|
||||
import annotations.{ModuleTarget, ReferenceTarget, Annotation, SingleTargetAnnotation}
|
||||
|
||||
import midas.targetutils.FirrtlFAMEModelAnnotation
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
class ChannelExcision extends Transform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
|
||||
val addedChannelAnnos = new mutable.ArrayBuffer[FAMEModelAnnotation]()
|
||||
val addedChannelAnnos = new mutable.ArrayBuffer[FirrtlFAMEModelAnnotation]()
|
||||
val pipeChannels = new mutable.HashMap[(ReferenceTarget, ReferenceTarget), String]()
|
||||
|
||||
|
||||
|
@ -29,19 +31,19 @@ class ChannelExcision extends Transform {
|
|||
def portTarget(p: Port) = topTarget.ref(p.name)
|
||||
|
||||
def onStmt(addedPorts: mutable.ArrayBuffer[Port])(s: Statement): Statement = s.map(onStmt(addedPorts)) match {
|
||||
case c @ Connect(_, lhs @ WSubField(WRef(lhsiname, _, InstanceKind, _), lhspname, _, _),
|
||||
rhs @ WSubField(WRef(rhsiname, _, InstanceKind, _), rhspname, _, _)) =>
|
||||
case c @ Connect(_, lhs @ WSubField(WRef(lhsiname, _, InstanceKind, _), lhspname, lType, _),
|
||||
rhs @ WSubField(WRef(rhsiname, _, InstanceKind, _), rhspname, rType, _)) =>
|
||||
val lhsTarget = subfieldTarget(lhsiname, lhspname)
|
||||
val rhsTarget = subfieldTarget(rhsiname, rhspname)
|
||||
pipeChannels.get((lhsTarget, rhsTarget)) match {
|
||||
case Some(chName) =>
|
||||
val srcP = Port(NoInfo, s"${rhsiname}_${rhspname}_source", Output, lhs.tpe)
|
||||
val sinkP = Port(NoInfo, s"${lhsiname}_${lhspname}_sink", Input, rhs.tpe)
|
||||
addedPorts ++= Seq(srcP, sinkP)
|
||||
renames.record(lhsTarget, portTarget(sinkP))
|
||||
renames.record(rhsTarget, portTarget(srcP))
|
||||
Block(Seq(Connect(NoInfo, lhs, WRef(sinkP)), Connect(NoInfo, WRef(srcP), rhs)))
|
||||
case None => c
|
||||
if (pipeChannels.contains((lhsTarget, rhsTarget)) || lType == ClockType) {
|
||||
val srcP = Port(NoInfo, s"${rhsiname}_${rhspname}_source", Output, lType)
|
||||
val sinkP = Port(NoInfo, s"${lhsiname}_${lhspname}_sink", Input, rType)
|
||||
addedPorts ++= Seq(srcP, sinkP)
|
||||
renames.record(lhsTarget, portTarget(sinkP))
|
||||
renames.record(rhsTarget, portTarget(srcP))
|
||||
Block(Seq(Connect(NoInfo, lhs, WRef(sinkP)), Connect(NoInfo, WRef(srcP), rhs)))
|
||||
} else {
|
||||
c
|
||||
}
|
||||
case s => s
|
||||
}
|
||||
|
@ -56,7 +58,7 @@ class ChannelExcision extends Transform {
|
|||
|
||||
// Step 1: Analysis -> build a map from reference targets to channel name
|
||||
state.annotations.collect({
|
||||
case fta@ FAMEChannelConnectionAnnotation(name, PipeChannel(_), Some(srcs), Some(sinks)) =>
|
||||
case fta@ FAMEChannelConnectionAnnotation(name, PipeChannel(_), _, Some(srcs), Some(sinks)) =>
|
||||
sinks.zip(srcs).foreach({ pipeChannels(_) = name })
|
||||
})
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ class ReadPort(val anno: ModelReadPort, val ports: Seq[Port]) extends IsMemoryPo
|
|||
val iSignals = Seq(anno.addr, anno.en)
|
||||
val iValids = iSignals.map(valid)
|
||||
Seq(
|
||||
Connect(NoInfo, readCmd.valid, Reduce.and(iValids)),
|
||||
Connect(NoInfo, readCmd.valid, And.reduce(iValids)),
|
||||
Connect(NoInfo, readCmd.bits("en"), bits(anno.en)),
|
||||
Connect(NoInfo, readCmd.bits("addr"), bits(anno.addr)),
|
||||
Connect(NoInfo, ready(anno.addr), readCmd.ready),
|
||||
|
@ -110,7 +110,7 @@ class WritePort(val anno: ModelWritePort, val ports: Seq[Port]) extends IsMemory
|
|||
iSignals.flatMap(rT => Seq(
|
||||
Connect(NoInfo, ready(rT), writeCmd.ready)
|
||||
)) ++ Seq(
|
||||
Connect(NoInfo, writeCmd.valid, Reduce.and(iValids)),
|
||||
Connect(NoInfo, writeCmd.valid, And.reduce(iValids)),
|
||||
Connect(NoInfo, writeCmd.bits("en"), bits(anno.en)),
|
||||
Connect(NoInfo, writeCmd.bits("addr"), bits(anno.addr)),
|
||||
Connect(NoInfo, writeCmd.bits("data"), bits(anno.data)),
|
||||
|
|
|
@ -11,6 +11,8 @@ import Utils._
|
|||
import firrtl.passes.MemPortUtils
|
||||
import annotations.{InstanceTarget, Annotation, SingleTargetAnnotation}
|
||||
|
||||
import midas.targetutils.FirrtlFAMEModelAnnotation
|
||||
|
||||
import scala.collection.mutable
|
||||
import mutable.{LinkedHashSet, LinkedHashMap}
|
||||
|
||||
|
@ -20,7 +22,7 @@ class ExtractModel extends Transform {
|
|||
|
||||
def promoteModels(state: CircuitState): CircuitState = {
|
||||
val anns = state.annotations.flatMap {
|
||||
case a @ FAMEModelAnnotation(it) if (it.module != it.circuit) => Seq(a, PromoteSubmoduleAnnotation(it))
|
||||
case a @ FirrtlFAMEModelAnnotation(it) if (it.module != it.circuit) => Seq(a, PromoteSubmoduleAnnotation(it))
|
||||
case a => Seq(a)
|
||||
}
|
||||
if (anns.toSeq == state.annotations.toSeq) {
|
||||
|
|
|
@ -8,6 +8,8 @@ import ir._
|
|||
import annotations._
|
||||
import collection.mutable.{ArrayBuffer, LinkedHashSet}
|
||||
|
||||
import midas.targetutils.FAMEAnnotation
|
||||
|
||||
// Assumes: AQB form
|
||||
// Run after ExtractModel
|
||||
// Label all unbound top-level ports as wire channels
|
||||
|
@ -21,16 +23,12 @@ class FAMEDefaults extends Transform {
|
|||
override def execute(state: CircuitState): CircuitState = {
|
||||
val analysis = new FAMEChannelAnalysis(state, FAME1Transform)
|
||||
val topModule = state.circuit.modules.find(_.name == state.circuit.main).get.asInstanceOf[Module]
|
||||
val globalSignals = state.annotations.collect({ case g: FAMEGlobalSignal => g.target.ref }).toSet
|
||||
val channelNames = state.annotations.collect({ case fca: FAMEChannelConnectionAnnotation => fca.globalName })
|
||||
val fameAnnos = state.annotations.collect({ case fa: FAMEAnnotation => fa }) // for performance, avoid other annos
|
||||
val globalSignals = fameAnnos.collect({ case g: FAMEGlobalSignal => g.target.ref }).toSet
|
||||
val channelNames = fameAnnos.collect({ case fca: FAMEChannelConnectionAnnotation => fca.globalName })
|
||||
val channelNS = Namespace(channelNames)
|
||||
def isGlobal(topPort: Port) = globalSignals.contains(topPort.name)
|
||||
def isBound(topPort: Port) = analysis.channelsByPort.contains(analysis.topTarget.ref(topPort.name))
|
||||
val defaultExtChannelAnnos = topModule.ports.filterNot(isGlobal).filterNot(isBound).flatMap({
|
||||
case Port(_, _, _, ClockType) => None // FIXME: Reject the clock in RC's debug interface
|
||||
case Port(_, name, Input, _) => Some(FAMEChannelConnectionAnnotation(channelNS.newName(name), WireChannel, None, Some(Seq(analysis.topTarget.ref(name)))))
|
||||
case Port(_, name, Output, _) => Some(FAMEChannelConnectionAnnotation(channelNS.newName(name), WireChannel, Some(Seq(analysis.topTarget.ref(name))), None))
|
||||
})
|
||||
val channelModules = new LinkedHashSet[String] // TODO: find modules to absorb into channels, don't label as FAME models
|
||||
val defaultLoopbackAnnos = new ArrayBuffer[FAMEChannelConnectionAnnotation]
|
||||
val defaultModelAnnos = new ArrayBuffer[FAMETransformAnnotation]
|
||||
|
@ -41,16 +39,16 @@ class FAMEDefaults extends Transform {
|
|||
wi
|
||||
case c @ Connect(_, WSubField(WRef(lhsiname, _, InstanceKind, _), lhspname, _, _), WSubField(WRef(rhsiname, _, InstanceKind, _), rhspname, _, _)) =>
|
||||
if (c.loc.tpe != ClockType && c.expr.tpe != ClockType) {
|
||||
defaultLoopbackAnnos += FAMEChannelConnectionAnnotation(
|
||||
defaultLoopbackAnnos += FAMEChannelConnectionAnnotation.implicitlyClockedLoopback(
|
||||
channelNS.newName(s"${rhsiname}_${rhspname}__to__${lhsiname}_${lhspname}"),
|
||||
WireChannel,
|
||||
Some(Seq(topTarget.ref(rhsiname).field(rhspname))),
|
||||
Some(Seq(topTarget.ref(lhsiname).field(lhspname))))
|
||||
Seq(topTarget.ref(rhsiname).field(rhspname)),
|
||||
Seq(topTarget.ref(lhsiname).field(lhspname)))
|
||||
}
|
||||
c
|
||||
case s => s
|
||||
}
|
||||
topModule.body.map(onStmt)
|
||||
state.copy(annotations = state.annotations ++ defaultExtChannelAnnos ++ defaultLoopbackAnnos ++ defaultModelAnnos)
|
||||
state.copy(annotations = state.annotations ++ defaultLoopbackAnnos ++ defaultModelAnnos)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import ir._
|
|||
import Mappers._
|
||||
import firrtl.Utils.{BoolType, kind, ceilLog2, one}
|
||||
import firrtl.passes.MemPortUtils
|
||||
import firrtl.transforms.DontTouchAnnotation
|
||||
import annotations._
|
||||
import scala.collection.mutable
|
||||
import mutable.{LinkedHashSet, LinkedHashMap}
|
||||
|
@ -26,202 +27,229 @@ trait FAME1Channel {
|
|||
def name: String
|
||||
def direction: Direction
|
||||
def ports: Seq[Port]
|
||||
def tpe: Type = FAMEChannelAnalysis.getHostDecoupledChannelType(name, ports)
|
||||
def portName: String
|
||||
def asPort: Port = Port(NoInfo, portName, direction, tpe)
|
||||
def isReady: Expression = WSubField(WRef(asPort), "ready", BoolType)
|
||||
def isValid: Expression = WSubField(WRef(asPort), "valid", BoolType)
|
||||
def isFiring: Expression = Reduce.and(Seq(isReady, isValid))
|
||||
def replacePortRef(wr: WRef): WSubField = {
|
||||
if (ports.size > 1) {
|
||||
WSubField(WSubField(WRef(asPort), "bits"), FAMEChannelAnalysis.removeCommonPrefix(wr.name, name)._1)
|
||||
} else {
|
||||
WSubField(WRef(asPort), "bits")
|
||||
}
|
||||
}
|
||||
def isValid: Expression
|
||||
def asHostModelPort: Option[Port] = None
|
||||
def replacePortRef(wr: WRef): Expression
|
||||
}
|
||||
|
||||
case class FAME1InputChannel(val name: String, val ports: Seq[Port]) extends FAME1Channel {
|
||||
trait InputChannel {
|
||||
this: FAME1Channel =>
|
||||
val direction = Input
|
||||
val portName = s"${name}_sink"
|
||||
def genTokenLogic(finishing: WRef): Seq[Statement] = {
|
||||
Seq(Connect(NoInfo, isReady, finishing))
|
||||
def setReady(readyCond: Expression): Statement
|
||||
}
|
||||
|
||||
trait HasModelPort {
|
||||
this: FAME1Channel =>
|
||||
override def isValid = WSubField(WRef(asHostModelPort.get), "valid", BoolType)
|
||||
def isReady = WSubField(WRef(asHostModelPort.get), "ready", BoolType)
|
||||
def isFiring: Expression = And(isReady, isValid)
|
||||
def setReady(advanceCycle: Expression): Statement = Connect(NoInfo, isReady, advanceCycle)
|
||||
|
||||
override def asHostModelPort: Option[Port] = {
|
||||
val tpe = FAMEChannelAnalysis.getHostDecoupledChannelType(name, ports)
|
||||
direction match {
|
||||
case Input => Some(Port(NoInfo, s"${name}_sink", Input, tpe))
|
||||
case Output => Some(Port(NoInfo, s"${name}_source", Output, tpe))
|
||||
}
|
||||
}
|
||||
|
||||
def replacePortRef(wr: WRef): Expression = {
|
||||
val payload = WSubField(WRef(asHostModelPort.get), "bits")
|
||||
if (ports.size == 1) payload else WSubField(payload, FAMEChannelAnalysis.removeCommonPrefix(wr.name, name)._1)
|
||||
}
|
||||
}
|
||||
|
||||
case class FAME1OutputChannel(val name: String, val ports: Seq[Port], val firedReg: DefRegister) extends FAME1Channel {
|
||||
trait FAME1DataChannel extends FAME1Channel with HasModelPort {
|
||||
def clockDomainEnable: Expression
|
||||
def firedReg: DefRegister
|
||||
def isFired = WRef(firedReg)
|
||||
def isFiredOrFiring = Or(isFired, isFiring)
|
||||
def updateFiredReg(finishing: WRef): Statement = {
|
||||
Connect(NoInfo, isFired, Mux(finishing, Negate(clockDomainEnable), isFiredOrFiring, BoolType))
|
||||
}
|
||||
}
|
||||
|
||||
case class FAME1ClockChannel(name: String, ports: Seq[Port]) extends FAME1Channel with InputChannel with HasModelPort
|
||||
|
||||
case class VirtualClockChannel(targetClock: Port) extends FAME1Channel with InputChannel {
|
||||
val name = "VirtualClockChannel"
|
||||
val ports = Seq(targetClock)
|
||||
val isValid: Expression = UIntLiteral(1)
|
||||
def setReady(advanceCycle: Expression): Statement = EmptyStmt
|
||||
def replacePortRef(wr: WRef): Expression = UIntLiteral(1)
|
||||
}
|
||||
|
||||
case class FAME1InputChannel(
|
||||
name: String,
|
||||
clockDomainEnable: Expression,
|
||||
ports: Seq[Port],
|
||||
firedReg: DefRegister) extends FAME1DataChannel with InputChannel {
|
||||
override def setReady(advanceCycle: Expression): Statement = {
|
||||
Connect(NoInfo, isReady, And(advanceCycle, Negate(isFired)))
|
||||
}
|
||||
}
|
||||
|
||||
case class FAME1OutputChannel(
|
||||
name: String,
|
||||
clockDomainEnable: Expression,
|
||||
ports: Seq[Port],
|
||||
firedReg: DefRegister) extends FAME1DataChannel {
|
||||
val direction = Output
|
||||
val portName = s"${name}_source"
|
||||
val isFired = WRef(firedReg)
|
||||
val isFiredOrFiring = Reduce.or(Seq(isFired, isFiring))
|
||||
def genTokenLogic(finishing: WRef, ccDeps: Iterable[FAME1InputChannel]): Seq[Statement] = {
|
||||
val regUpdate = Connect(
|
||||
NoInfo,
|
||||
isFired,
|
||||
Mux(finishing,
|
||||
UIntLiteral(0, IntWidth(1)),
|
||||
isFiredOrFiring,
|
||||
BoolType))
|
||||
val setValid = Connect(
|
||||
NoInfo,
|
||||
isValid,
|
||||
Reduce.and(ccDeps.map(_.isValid) ++ Seq(Negate(isFired))))
|
||||
Seq(regUpdate, setValid)
|
||||
}
|
||||
}
|
||||
|
||||
object ChannelCCDependencyGraph {
|
||||
def apply(m: Module): LinkedHashMap[FAME1OutputChannel, LinkedHashSet[FAME1InputChannel]] = {
|
||||
new LinkedHashMap[FAME1OutputChannel, LinkedHashSet[FAME1InputChannel]]
|
||||
}
|
||||
}
|
||||
|
||||
object PatientMemTransformer {
|
||||
def apply(mem: DefMemory, finishing: Expression, memClock: WRef, ns: Namespace): Block = {
|
||||
val shim = DefWire(NoInfo, mem.name, MemPortUtils.memType(mem))
|
||||
val newMem = mem.copy(name = ns.newName(mem.name))
|
||||
val defaultConnect = Connect(NoInfo, WRef(shim), WRef(newMem.name, shim.tpe, MemKind))
|
||||
val syncReadPorts = (newMem.readers ++ newMem.readwriters).filter(rp => mem.readLatency > 0)
|
||||
val preserveReads = syncReadPorts.flatMap {
|
||||
case rpName =>
|
||||
val addrWidth = IntWidth(ceilLog2(mem.depth) max 1)
|
||||
val dummyReset = DefWire(NoInfo, ns.newName(s"${mem.name}_${rpName}_dummyReset"), BoolType)
|
||||
val tieOff = Connect(NoInfo, WRef(dummyReset), UIntLiteral(0))
|
||||
val addrReg = new DefRegister(NoInfo, ns.newName(s"${mem.name}_${rpName}"),
|
||||
UIntType(addrWidth), memClock, WRef(dummyReset), UIntLiteral(0, addrWidth))
|
||||
val updateReg = Connect(NoInfo, WRef(addrReg), WSubField(WSubField(WRef(shim), rpName), "addr"))
|
||||
val useReg = Connect(NoInfo, MemPortUtils.memPortField(newMem, rpName, "addr"), WRef(addrReg))
|
||||
Seq(dummyReset, tieOff, addrReg, Conditionally(NoInfo, finishing, updateReg, useReg))
|
||||
}
|
||||
val gateWrites = (newMem.writers ++ newMem.readwriters).map {
|
||||
case wpName =>
|
||||
Conditionally(
|
||||
NoInfo,
|
||||
Negate(finishing),
|
||||
Connect(NoInfo, MemPortUtils.memPortField(newMem, wpName, "en"), UIntLiteral(0, IntWidth(1))),
|
||||
EmptyStmt)
|
||||
}
|
||||
new Block(Seq(shim, newMem, defaultConnect) ++ preserveReads ++ gateWrites)
|
||||
}
|
||||
}
|
||||
|
||||
object PatientSSMTransformer {
|
||||
def apply(m: Module, analysis: FAMEChannelAnalysis)(implicit triggerName: String): Module = {
|
||||
val ns = Namespace(m)
|
||||
val clocks = m.ports.filter(_.tpe == ClockType)
|
||||
// TODO: turn this back on
|
||||
// assert(clocks.length == 1)
|
||||
val finishing = new Port(NoInfo, ns.newName(triggerName), Input, BoolType)
|
||||
val hostClock = clocks.find(_.name == "clock").getOrElse(clocks.head) // TODO: naming convention for host clock
|
||||
def onStmt(stmt: Statement): Statement = stmt.map(onStmt) match {
|
||||
case conn @ Connect(info, lhs, _) if (kind(lhs) == RegKind) =>
|
||||
Conditionally(info, WRef(finishing), conn, EmptyStmt)
|
||||
case s: Stop => s.copy(en = DoPrim(PrimOps.And, Seq(WRef(finishing), s.en), Seq.empty, BoolType))
|
||||
case p: Print => p.copy(en = DoPrim(PrimOps.And, Seq(WRef(finishing), p.en), Seq.empty, BoolType))
|
||||
case mem: DefMemory => PatientMemTransformer(mem, WRef(finishing), WRef(hostClock), ns)
|
||||
case wi: WDefInstance if analysis.syncNativeModules.contains(analysis.moduleTarget(wi)) =>
|
||||
new Block(Seq(wi, Connect(wi.info, WSubField(WRef(wi), triggerName), WRef(finishing))))
|
||||
case s => s
|
||||
}
|
||||
Module(m.info, m.name, m.ports :+ finishing, m.body.map(onStmt))
|
||||
def setValid(finishing: WRef, ccDeps: Iterable[FAME1InputChannel]): Statement = {
|
||||
Connect(NoInfo, isValid, And.reduce(ccDeps.map(_.isValid).toSeq :+ Negate(isFired)))
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-clock timestep:
|
||||
// When finishing is high, dequeue token from clock channel
|
||||
// - Use to initialize isFired for all channels (with negation)
|
||||
// - Finishing is gated with clock channel valid
|
||||
object FAMEModuleTransformer {
|
||||
def apply(m: Module, analysis: FAMEChannelAnalysis)(implicit triggerName: String): Module = {
|
||||
// Step 0: Special signals & bookkeeping
|
||||
def apply(m: Module, analysis: FAMEChannelAnalysis): Module = {
|
||||
// Step 0: Bookkeeping for port structure conventions
|
||||
implicit val ns = Namespace(m)
|
||||
val clocks = m.ports.filter(_.tpe == ClockType)
|
||||
// TODO: turn this back to == 1
|
||||
assert(clocks.length >= 1)
|
||||
val hostClock = clocks.find(_.name == "clock").getOrElse(clocks.head) // TODO: naming convention for host clock
|
||||
val hostReset = HostReset.makePort(ns)
|
||||
def createHostReg(name: String = "host", width: Width = IntWidth(1)): DefRegister = {
|
||||
new DefRegister(NoInfo, ns.newName(name), UIntType(width), WRef(hostClock), WRef(hostReset), UIntLiteral(0, width))
|
||||
}
|
||||
val finishing = DefWire(NoInfo, ns.newName(triggerName), BoolType)
|
||||
/*
|
||||
* The conjuction of the following expressions enables the target-clock
|
||||
* Failing to keep the target clock-gated during reset can lead to spurious
|
||||
* target-clock edges leading to non-deterministically initialized target state
|
||||
* in registers that are not reset
|
||||
*/
|
||||
val clockGatePredicates = Seq(WRef(finishing), DoPrim(PrimOps.Not, Seq(WRef(hostReset)), Seq.empty, BoolType))
|
||||
val buf = InstanceInfo(DefineAbstractClockGate.blackbox).connect("I", WRef(hostClock))
|
||||
.connect("CE", DoPrim(PrimOps.And, clockGatePredicates, Seq.empty, BoolType))
|
||||
val targetClock = SignalInfo(buf.decl, buf.assigns, WSubField(buf.ref, "O", ClockType, SourceFlow))
|
||||
|
||||
// Step 1: Build channels
|
||||
val mTarget = ModuleTarget(analysis.circuit.main, m.name)
|
||||
val inChannels = (analysis.modelInputChannelPortMap(mTarget)).map({
|
||||
case(cName, ports) => new FAME1InputChannel(cName, ports)
|
||||
})
|
||||
val inChannelMap = new LinkedHashMap[String, FAME1InputChannel] ++
|
||||
(inChannels.flatMap(c => c.ports.map(p => (p.name, c))))
|
||||
val clocks: Seq[Port] = m.ports.filter(_.tpe == ClockType)
|
||||
val portsByName = m.ports.map(p => p.name -> p).toMap
|
||||
assert(clocks.length >= 1)
|
||||
|
||||
val outChannels = analysis.modelOutputChannelPortMap(mTarget).map({
|
||||
case(cName, ports) =>
|
||||
val firedReg = createHostReg(name = ns.newName(s"${cName}_fired"))
|
||||
new FAME1OutputChannel(cName, ports, firedReg)
|
||||
})
|
||||
val outChannelMap = new LinkedHashMap[String, FAME1OutputChannel] ++
|
||||
(outChannels.flatMap(c => c.ports.map(p => (p.name, c))))
|
||||
val decls = Seq(finishing) ++ outChannels.map(_.firedReg)
|
||||
// Multi-clock management step 1: Add host clock + reset ports, finishing wire
|
||||
// TODO: Should finishing be a WrappedComponent?
|
||||
// TODO: Avoid static naming convention.
|
||||
val hostReset = Port(NoInfo, WrapTop.hostResetName, Input, BoolType)
|
||||
val hostClock = Port(NoInfo, WrapTop.hostClockName, Input, ClockType)
|
||||
val finishing = DefWire(NoInfo, "targetCycleFinishing", BoolType)
|
||||
assert(ns.tryName(hostReset.name) && ns.tryName(hostClock.name) && ns.tryName(finishing.name))
|
||||
def hostFlagReg(suggestName: String, resetVal: UIntLiteral = UIntLiteral(0)): DefRegister = {
|
||||
DefRegister(NoInfo, ns.newName(suggestName), BoolType, WRef(hostClock), WRef(hostReset), resetVal)
|
||||
}
|
||||
|
||||
// Multi-clock management step 2: Build clock flags and clock channel
|
||||
def isClockChannel(info: (String, (Option[Port], Seq[Port]))) = info match {
|
||||
case (_, (clk, ports)) => clk.isEmpty && ports.forall(_.tpe == ClockType)
|
||||
}
|
||||
|
||||
|
||||
// Step 2: Find combinational dependencies
|
||||
val ccChecker = new firrtl.transforms.CheckCombLoops
|
||||
val clockChannel = analysis.modelInputChannelPortMap(mTarget).find(isClockChannel) match {
|
||||
case Some((name, (None, ports))) => FAME1ClockChannel(name, ports)
|
||||
case Some(_) => ??? // Clock channel cannot have an associated clock domain
|
||||
case None => VirtualClockChannel(clocks.head) // Virtual clock channel for single-clock models
|
||||
}
|
||||
|
||||
/*
|
||||
* NB: Failing to keep the target clock-gated during FPGA initialization
|
||||
* can lead to spurious updates or metastability in target state elements.
|
||||
* Keeping all target-clocks gated through the latter stages of FPGA
|
||||
* initialization and reset ensures all target state elements are
|
||||
* initialized with a deterministic set of initial values.
|
||||
*/
|
||||
val nReset = DoPrim(PrimOps.Not, Seq(WRef(hostReset)), Seq.empty, BoolType)
|
||||
|
||||
// Multi-clock management step 4: Generate clock buffers for all target clocks
|
||||
val targetClockBufs: Seq[SignalInfo] = clockChannel.ports.map { en =>
|
||||
val enableReg = hostFlagReg(s"${en.name}_enabled", resetVal = UIntLiteral(1))
|
||||
val buf = WDefInstance(ns.newName(s"${en.name}_buffer"), DefineAbstractClockGate.blackbox.name)
|
||||
val clockFlag = DoPrim(PrimOps.AsUInt, Seq(clockChannel.replacePortRef(WRef(en))), Nil, BoolType)
|
||||
val connects = Block(Seq(
|
||||
Connect(NoInfo, WRef(enableReg), Mux(WRef(finishing), clockFlag, WRef(enableReg), BoolType)),
|
||||
Connect(NoInfo, WSubField(WRef(buf), "I"), WRef(hostClock)),
|
||||
Connect(NoInfo, WSubField(WRef(buf), "CE"), And(And(WRef(enableReg), WRef(finishing)), nReset))))
|
||||
SignalInfo(Block(Seq(enableReg, buf)), connects, WSubField(WRef(buf), "O", ClockType, SourceFlow))
|
||||
}
|
||||
|
||||
// Multi-clock management step 5: Generate target clock substitution map
|
||||
def asWE(p: Port) = WrappedExpression.we(WRef(p))
|
||||
val replaceClocksMap = (clockChannel.ports.map(p => asWE(p)) zip targetClockBufs.map(_.ref)).toMap
|
||||
|
||||
// LI-BDN transformation step 1: Build channels
|
||||
// TODO: get rid of the analysis calls; we just need connectivity & annotations
|
||||
val portDeps = analysis.connectivity(m.name)
|
||||
|
||||
def genMetadata(info: (String, (Option[Port], Seq[Port]))) = info match {
|
||||
case (cName, (Some(clock), ports)) =>
|
||||
// must be driven by one clock input port
|
||||
// TODO: this should not include muxes in connectivity!
|
||||
val srcClockPorts = portDeps.getEdges(clock.name).map(portsByName(_))
|
||||
assert(srcClockPorts.size == 1)
|
||||
val clockRef = WRef(srcClockPorts.head)
|
||||
val clockFlag = DoPrim(PrimOps.AsUInt, Seq(clockChannel.replacePortRef(clockRef)), Nil, BoolType)
|
||||
val firedReg = hostFlagReg(suggestName = ns.newName(s"${cName}_fired"))
|
||||
(cName, clockFlag, ports, firedReg)
|
||||
case (cName, (None, ports)) => clockChannel match {
|
||||
case vc: VirtualClockChannel =>
|
||||
val firedReg = hostFlagReg(suggestName = ns.newName(s"${cName}_fired"))
|
||||
(cName, UIntLiteral(1), ports, firedReg)
|
||||
case _ =>
|
||||
throw new RuntimeException(s"Channel ${cName} has no associated clock.")
|
||||
}
|
||||
}
|
||||
|
||||
// LinkedHashMap.from is 2.13-only :(
|
||||
def stableMap[K, V](contents: Iterable[(K, V)]) = new LinkedHashMap[K, V] ++= contents
|
||||
|
||||
// Have to filter out the clock channel from the input channels
|
||||
val inChannelInfo = analysis.modelInputChannelPortMap(mTarget).filterNot(isClockChannel(_)).toSeq
|
||||
val inChannelMetadata = inChannelInfo.map(genMetadata(_))
|
||||
val inChannels = inChannelMetadata.map((FAME1InputChannel.apply _).tupled)
|
||||
val inChannelMap = stableMap(inChannels.flatMap(c => c.ports.map(p => p.name -> c)))
|
||||
|
||||
val outChannelInfo = analysis.modelOutputChannelPortMap(mTarget).toSeq
|
||||
val outChannelMetadata = outChannelInfo.map(genMetadata(_))
|
||||
val outChannels = outChannelMetadata.map((FAME1OutputChannel.apply _).tupled)
|
||||
val outChannelMap = stableMap(outChannels.flatMap(c => c.ports.map(p => p.name -> c)))
|
||||
|
||||
// LI-BDN transformation step 2: find combinational dependencies among channels
|
||||
val ccDeps = new LinkedHashMap[FAME1OutputChannel, LinkedHashSet[FAME1InputChannel]]
|
||||
portDeps.getEdgeMap.collect({ case (o, iSet) if outChannelMap.contains(o) =>
|
||||
// Only add input channels, since output might depend on output RHS ref
|
||||
ccDeps.getOrElseUpdate(outChannelMap(o), new LinkedHashSet[FAME1InputChannel]) ++= iSet.flatMap(inChannelMap.get(_))
|
||||
})
|
||||
|
||||
// Step 3: transform ports
|
||||
val transformedPorts = clocks ++ Seq(hostReset) ++ inChannels.map(_.asPort) ++ outChannels.map(_.asPort)
|
||||
// LI-BDN transformation step 3: transform ports (includes new clock ports)
|
||||
val transformedPorts = hostClock +: hostReset +: (clockChannel +: inChannels ++: outChannels).flatMap(_.asHostModelPort)
|
||||
|
||||
// Step 4: Replace refs and gate state updates
|
||||
// LI-BDN transformation step 4: replace port and clock references and gate state updates
|
||||
val clockChannelPortNames = clockChannel.ports.map(_.name).toSet
|
||||
def onExpr(expr: Expression): Expression = expr.map(onExpr) match {
|
||||
case iWR @ WRef(name, tpe, PortKind, SourceFlow) if tpe != ClockType =>
|
||||
case iWR @ WRef(name, tpe, PortKind, SourceFlow) if tpe != ClockType =>
|
||||
// Generally SourceFlow references to ports will be input channels, but RTL may use
|
||||
// an assignment to an output port as something akin to a wire, so check output ports too.
|
||||
inChannelMap.getOrElse(name, outChannelMap(name)).replacePortRef(iWR)
|
||||
case oWR @ WRef(name, tpe, PortKind, SinkFlow) if tpe != ClockType =>
|
||||
outChannelMap(name).replacePortRef(oWR)
|
||||
case wr: WRef if wr.name == hostClock.name =>
|
||||
// Replace host clock references with target clock references
|
||||
targetClock.ref
|
||||
case e => e
|
||||
case cWR @ WRef(name, ClockType, PortKind, SourceFlow) if clockChannelPortNames(name) =>
|
||||
replaceClocksMap(WrappedExpression.we(cWR))
|
||||
case e => e map onExpr
|
||||
}
|
||||
|
||||
// This is vestigial from when we supported two means of implementing clock
|
||||
// gating; This will be removed when multiclock is implemented
|
||||
val targetStateTrigger = one
|
||||
|
||||
def onStmt(stmt: Statement): Statement = stmt.map(onStmt).map(onExpr) match {
|
||||
case conn @ Connect(info, lhs, _) if (kind(lhs) == RegKind) =>
|
||||
Conditionally(info, targetStateTrigger, conn, EmptyStmt)
|
||||
case mem: DefMemory => PatientMemTransformer(mem, targetStateTrigger, WRef(hostClock), ns)
|
||||
case wi: WDefInstance if analysis.syncNativeModules.contains(analysis.moduleTarget(wi)) =>
|
||||
new Block(Seq(wi, Connect(wi.info, WSubField(WRef(wi), triggerName), targetStateTrigger)))
|
||||
case s: Stop => s.copy(en = DoPrim(PrimOps.And, Seq(targetStateTrigger, s.en), Seq.empty, BoolType))
|
||||
case p: Print => p.copy(en = DoPrim(PrimOps.And, Seq(targetStateTrigger, p.en), Seq.empty, BoolType))
|
||||
case s => s
|
||||
def onStmt(stmt: Statement): Statement = stmt match {
|
||||
case Connect(info, WRef(name, ClockType, PortKind, flow), rhs) =>
|
||||
// Don't substitute gated clock for LHS expressions
|
||||
Connect(info, WRef(name, ClockType, WireKind, flow), onExpr(rhs))
|
||||
case s => s map onStmt map onExpr
|
||||
}
|
||||
|
||||
val transformedStmts = Seq(m.body.map(onStmt))
|
||||
val updatedBody = onStmt(m.body)
|
||||
|
||||
// Step 5: Add firing rules for output channels, trigger end of cycle
|
||||
val ruleStmts = new mutable.ArrayBuffer[Statement]
|
||||
ruleStmts ++= outChannels.flatMap(o => o.genTokenLogic(WRef(finishing), ccDeps(o)))
|
||||
ruleStmts ++= inChannels.flatMap(i => i.genTokenLogic(WRef(finishing)))
|
||||
ruleStmts += Connect(NoInfo, WRef(finishing),
|
||||
Reduce.and(outChannels.map(_.isFiredOrFiring) ++ inChannels.map(_.isValid)))
|
||||
// LI-BDN transformation step 5: add firing rules for output channels, trigger end of cycle
|
||||
// This is modified for multi-clock, as each channel fires only when associated clock is enabled
|
||||
val allFiredOrFiring = And.reduce(outChannels.map(_.isFiredOrFiring) ++ inChannels.map(_.isValid))
|
||||
|
||||
val channelStateRules = (inChannels ++ outChannels).map(c => c.updateFiredReg(WRef(finishing)))
|
||||
val inputRules = inChannels.map(i => i.setReady(WRef(finishing)))
|
||||
val outputRules = outChannels.map(o => o.setValid(WRef(finishing), ccDeps(o)))
|
||||
val topRules = Seq(clockChannel.setReady(allFiredOrFiring),
|
||||
Connect(NoInfo, WRef(finishing), And(allFiredOrFiring, clockChannel.isValid)))
|
||||
|
||||
// Keep output clock ports around as wires just for convenience to keep connects legal
|
||||
val clockOutputsAsWires = m.ports.collect { case Port(i, n, Output, ClockType) => DefWire(i, n, ClockType) }
|
||||
|
||||
// Statements have to be conservatively ordered to satisfy declaration order
|
||||
val allStmts = targetClock.decl +: (decls ++ transformedStmts ++ ruleStmts) :+ targetClock.assigns
|
||||
Module(m.info, m.name, transformedPorts, new Block(allStmts))
|
||||
val decls = finishing +: clockOutputsAsWires ++: targetClockBufs.map(_.decl) ++: (inChannels ++ outChannels).map(_.firedReg)
|
||||
val assigns = targetClockBufs.map(_.assigns) ++ channelStateRules ++ inputRules ++ outputRules ++ topRules
|
||||
Module(m.info, m.name, transformedPorts, Block(decls ++: updatedBody +: assigns))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,8 +259,10 @@ class FAMETransform extends Transform {
|
|||
|
||||
def updateNonChannelConnects(analysis: FAMEChannelAnalysis)(stmt: Statement): Statement = stmt.map(updateNonChannelConnects(analysis)) match {
|
||||
case wi: WDefInstance if (analysis.transformedModules.contains(analysis.moduleTarget(wi))) =>
|
||||
val resetConn = Connect(NoInfo, WSubField(WRef(wi), "hostReset"), WRef(analysis.hostReset.ref, BoolType))
|
||||
Block(Seq(wi, resetConn))
|
||||
val clockConn = Connect(NoInfo, WSubField(WRef(wi), WrapTop.hostClockName), WRef(analysis.hostClock.ref, ClockType))
|
||||
val resetConn = Connect(NoInfo, WSubField(WRef(wi), WrapTop.hostResetName), WRef(analysis.hostReset.ref, BoolType))
|
||||
Block(Seq(wi, clockConn, resetConn))
|
||||
case Connect(_, lhs, rhs) if (lhs.tpe == ClockType) => EmptyStmt // drop ancillary clock connects
|
||||
case Connect(_, WRef(name, _, _, _), _) if (analysis.staleTopPorts.contains(analysis.topTarget.ref(name))) => EmptyStmt
|
||||
case Connect(_, _, WRef(name, _, _, _)) if (analysis.staleTopPorts.contains(analysis.topTarget.ref(name))) => EmptyStmt
|
||||
case s => s
|
||||
|
@ -254,9 +284,9 @@ class FAMETransform extends Transform {
|
|||
analysis.sourcePorts(c).map(rt => (rt, rt.copy(ref = s"${c}_source").field("bits").field(FAMEChannelAnalysis.removeCommonPrefix(rt.ref, c)._1)))
|
||||
})
|
||||
|
||||
def renamePorts(suffix: String, lookup: ModuleTarget => Map[String, Seq[Port]])
|
||||
def renamePorts(suffix: String, lookup: ModuleTarget => Map[String, (Option[Port], Seq[Port])])
|
||||
(mT: ModuleTarget): Seq[(ReferenceTarget, ReferenceTarget)] = {
|
||||
lookup(mT).toSeq.flatMap({ case (cName, pList) =>
|
||||
lookup(mT).toSeq.flatMap({ case (cName, (clockOption, pList)) =>
|
||||
pList.map({ port =>
|
||||
val decoupledTarget = mT.ref(s"${cName}${suffix}").field("bits")
|
||||
if (pList.size == 1)
|
||||
|
@ -264,6 +294,7 @@ class FAMETransform extends Transform {
|
|||
else
|
||||
(mT.ref(port.name), decoupledTarget.field(FAMEChannelAnalysis.removeCommonPrefix(port.name, cName)._1))
|
||||
})
|
||||
// TODO: rename clock to nothing, since it is deleted
|
||||
})
|
||||
}
|
||||
def renameModelInputs: ModuleTarget => Seq[(ReferenceTarget, ReferenceTarget)] = renamePorts("_sink", analysis.modelInputChannelPortMap)
|
||||
|
@ -292,12 +323,20 @@ class FAMETransform extends Transform {
|
|||
val analysis = new FAMEChannelAnalysis(state, FAME1Transform)
|
||||
// TODO: pick a value that does not collide
|
||||
implicit val triggerName = "finishing"
|
||||
|
||||
val toTransform = analysis.transformedModules
|
||||
val transformedModules = c.modules.map {
|
||||
case m: Module if (m.name == c.main) => transformTop(m, analysis)
|
||||
case m: Module if (analysis.transformedModules.contains(ModuleTarget(c.main,m.name))) => FAMEModuleTransformer(m, analysis)
|
||||
case m: Module if (analysis.syncNativeModules.contains(ModuleTarget(c.main, m.name))) => PatientSSMTransformer(m, analysis)
|
||||
case m => m
|
||||
case m: Module if (toTransform.contains(ModuleTarget(c.main, m.name))) => FAMEModuleTransformer(m, analysis)
|
||||
case m => m // TODO (Albert): revisit this; currently, not transforming nested modules
|
||||
}
|
||||
state.copy(circuit = c.copy(modules = transformedModules), renames = Some(hostDecouplingRenames(analysis)))
|
||||
|
||||
val filteredAnnos = state.annotations.filter {
|
||||
case DontTouchAnnotation(rt) if toTransform.contains(rt.moduleTarget) => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
val newCircuit = c.copy(modules = transformedModules)
|
||||
CircuitState(newCircuit, outputForm, filteredAnnos, Some(hostDecouplingRenames(analysis)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import firrtl._
|
|||
import ir._
|
||||
import Utils._
|
||||
import Mappers._
|
||||
import traversals.Foreachers._
|
||||
import graph.DiGraph
|
||||
import analyses.InstanceGraph
|
||||
import transforms.CheckCombLoops
|
||||
|
@ -50,11 +51,6 @@ private[fame] object FAMEChannelAnalysis {
|
|||
def getHostDecoupledChannelType(name: String, ports: Seq[Port]): Type = Decouple(getHostDecoupledChannelPayloadType(name, ports))
|
||||
}
|
||||
|
||||
private [fame] object HostReset {
|
||||
def makePort(ns: Namespace): Port =
|
||||
new Port(NoInfo, ns.newName("hostReset"), Input, Utils.BoolType)
|
||||
}
|
||||
|
||||
private[fame] class FAMEChannelAnalysis(val state: CircuitState, val fameType: FAMETransformType) {
|
||||
// TODO: only transform submodules of model modules
|
||||
// TODO: add renames!
|
||||
|
@ -101,6 +97,7 @@ private[fame] class FAMEChannelAnalysis(val state: CircuitState, val fameType: F
|
|||
transformedModules += mt
|
||||
case fca: FAMEChannelConnectionAnnotation =>
|
||||
channels += fca.globalName
|
||||
fca.clock.foreach({ rt => channelsByPort(rt) = fca.globalName })
|
||||
fca.sinks.toSeq.flatten.foreach({ rt => channelsByPort(rt) = fca.globalName })
|
||||
fca.sources.toSeq.flatten.foreach({ rt => channelsByPort(rt) = fca.globalName })
|
||||
})
|
||||
|
@ -109,11 +106,17 @@ private[fame] class FAMEChannelAnalysis(val state: CircuitState, val fameType: F
|
|||
val topConnects = new LinkedHashMap[ReferenceTarget, ReferenceTarget]
|
||||
val inputChannels = new LinkedHashMap[ModuleTarget, mutable.Set[String]] with MultiMap[ModuleTarget, String]
|
||||
val outputChannels = new LinkedHashMap[ModuleTarget, mutable.Set[String]] with MultiMap[ModuleTarget, String]
|
||||
moduleNodes(topTarget).asInstanceOf[Module].body.map(getTopConnects)
|
||||
def getTopConnects(stmt: Statement): Statement = stmt.map(getTopConnects) match {
|
||||
getTopConnects(moduleNodes(topTarget).asInstanceOf[Module].body)
|
||||
|
||||
def getTopConnects(stmt: Statement): Unit = stmt match {
|
||||
case WDefInstance(_, iname, mname, _) =>
|
||||
moduleOfInstance(iname) = mname
|
||||
EmptyStmt
|
||||
case Connect(_, WRef(tpname, ClockType, _, _), WSubField(WRef(iname, _, _, _), pname, ClockType, _)) =>
|
||||
// Clock connect, don't make any channels
|
||||
// The clock in a FAMEChannelConnectionAnnotation is the clock from model to bridge
|
||||
val tpRef = topTarget.ref(tpname)
|
||||
val child = topTarget.instOf(iname, moduleOfInstance(iname))
|
||||
topConnects(tpRef) = child.ref(pname)
|
||||
case Connect(_, WRef(tpname, _, _, _), WSubField(WRef(iname, _, _, _), pname, _, _)) =>
|
||||
val tpRef = topTarget.ref(tpname)
|
||||
channelsByPort.get(tpRef).foreach({ cname =>
|
||||
|
@ -121,7 +124,6 @@ private[fame] class FAMEChannelAnalysis(val state: CircuitState, val fameType: F
|
|||
topConnects(tpRef) = child.ref(pname)
|
||||
outputChannels.addBinding(child.ofModuleTarget, cname)
|
||||
})
|
||||
EmptyStmt
|
||||
case Connect(_, WSubField(WRef(iname, _, _, _), pname, _, _), WRef(tpname, _, _, _)) =>
|
||||
val tpRef = topTarget.ref(tpname)
|
||||
channelsByPort.get(tpRef).foreach({ cname =>
|
||||
|
@ -129,20 +131,27 @@ private[fame] class FAMEChannelAnalysis(val state: CircuitState, val fameType: F
|
|||
topConnects(tpRef) = child.ref(pname)
|
||||
inputChannels.addBinding(child.ofModuleTarget, cname)
|
||||
})
|
||||
EmptyStmt
|
||||
case s => EmptyStmt
|
||||
case s => s.foreach(getTopConnects)
|
||||
}
|
||||
|
||||
val transformedSinks = new LinkedHashSet[String]
|
||||
val transformedSources = new LinkedHashSet[String]
|
||||
val sinkModel = new LinkedHashMap[String, InstanceTarget]
|
||||
val sourceModel = new LinkedHashMap[String, InstanceTarget]
|
||||
|
||||
// clock ports don't go from one model to the other -> only one map needed
|
||||
val modelClockPort = new LinkedHashMap[String, Option[ReferenceTarget]]
|
||||
val sinkPorts = new LinkedHashMap[String, Seq[ReferenceTarget]]
|
||||
val sourcePorts = new LinkedHashMap[String, Seq[ReferenceTarget]]
|
||||
val staleTopPorts = new LinkedHashSet[ReferenceTarget]
|
||||
state.annotations.collect({
|
||||
case fca: FAMEChannelConnectionAnnotation =>
|
||||
channels += fca.globalName
|
||||
|
||||
// Clock port always gets recorded and marked for deletion in FAME transform
|
||||
modelClockPort(fca.globalName) = fca.clock
|
||||
staleTopPorts ++= fca.clock
|
||||
|
||||
val sinks = fca.sinks.toSeq.flatten
|
||||
sinkPorts(fca.globalName) = sinks
|
||||
sinks.headOption.filter(rt => transformedModules.contains(ModuleTarget(rt.circuit, topConnects(rt).encapsulatingModule))).foreach({ rt =>
|
||||
|
@ -151,6 +160,7 @@ private[fame] class FAMEChannelAnalysis(val state: CircuitState, val fameType: F
|
|||
transformedSinks += fca.globalName
|
||||
staleTopPorts ++= sinks
|
||||
})
|
||||
|
||||
val sources = fca.sources.toSeq.flatten
|
||||
sourcePorts(fca.globalName) = sources
|
||||
sources.headOption.filter(rt => transformedModules.contains(ModuleTarget(rt.circuit, topConnects(rt).encapsulatingModule))).foreach({ rt =>
|
||||
|
@ -161,42 +171,48 @@ private[fame] class FAMEChannelAnalysis(val state: CircuitState, val fameType: F
|
|||
})
|
||||
})
|
||||
|
||||
val hostClock = state.annotations.collect({ case FAMEHostClock(rt) => rt }).head
|
||||
val hostReset = state.annotations.collect({ case FAMEHostReset(rt) => rt }).head
|
||||
|
||||
def inputPortsByChannel(mTarget: ModuleTarget): Map[String, Seq[Port]] = {
|
||||
private def irPortFromGlobalTarget(mt: ModuleTarget)(rt: ReferenceTarget): Option[Port] = {
|
||||
val modelPort = topConnects(rt).pathlessTarget
|
||||
Some(modelPort).filter(_.module == mt.module).map(portNodes(_))
|
||||
}
|
||||
|
||||
def portsByInputChannel(mTarget: ModuleTarget): Map[String, (Option[Port], Seq[Port])] = {
|
||||
val iChannels = inputChannels.get(mTarget).toSet.flatten
|
||||
iChannels.map({
|
||||
cname => (cname, sinkPorts(cname).map(topConnects(_).pathlessTarget).map(portNodes(_)))
|
||||
cname => (cname, (modelClockPort(cname).flatMap(irPortFromGlobalTarget(mTarget)), sinkPorts(cname).map(rt => irPortFromGlobalTarget(mTarget)(rt).get)))
|
||||
}).toMap
|
||||
}
|
||||
|
||||
def outputPortsByChannel(mTarget: ModuleTarget): Map[String, Seq[Port]] = {
|
||||
def portsByOutputChannel(mTarget: ModuleTarget): Map[String, (Option[Port], Seq[Port])] = {
|
||||
val oChannels = outputChannels.get(mTarget).toSet.flatten
|
||||
oChannels.map({
|
||||
cname => (cname, sourcePorts(cname).map(topConnects(_).pathlessTarget).map(portNodes(_)))
|
||||
cname => (cname, (modelClockPort(cname).flatMap(irPortFromGlobalTarget(mTarget)), sourcePorts(cname).map(rt => irPortFromGlobalTarget(mTarget)(rt).get)))
|
||||
}).toMap
|
||||
}
|
||||
|
||||
lazy val modelPorts = {
|
||||
val mPorts = new LinkedHashMap[ModuleTarget, mutable.Set[FAMEChannelPortsAnnotation]] with MultiMap[ModuleTarget, FAMEChannelPortsAnnotation]
|
||||
state.annotations.collect({
|
||||
case fcp@FAMEChannelPortsAnnotation(_, port :: ps) => mPorts.addBinding(port.moduleTarget, fcp)
|
||||
case fcp @ FAMEChannelPortsAnnotation(_, _, port :: ps) => mPorts.addBinding(port.moduleTarget, fcp)
|
||||
})
|
||||
mPorts
|
||||
}
|
||||
|
||||
// Looks up all FAMEChannelPortAnnotations bound to a model module, to generate a Map
|
||||
// from channel name to port list
|
||||
private def genModelChannelPortMap(direction: Option[Direction])(mTarget: ModuleTarget): Map[String, Seq[Port]] = {
|
||||
// from channel name to clock option and port list
|
||||
private def genModelChannelPortMap(direction: Option[Direction])(mTarget: ModuleTarget): Map[String, (Option[Port], Seq[Port])] = {
|
||||
modelPorts(mTarget).collect({
|
||||
case FAMEChannelPortsAnnotation(name, ports) if direction == None || portNodes(ports.head).direction == direction.get =>
|
||||
(name, ports.map(portNodes(_)))
|
||||
case FAMEChannelPortsAnnotation(name, clock, ports) if direction == None || portNodes(ports.head).direction == direction.get =>
|
||||
(name, (clock.map(portNodes(_)), ports.map(portNodes(_))))
|
||||
}).toMap
|
||||
}
|
||||
|
||||
def modelInputChannelPortMap: ModuleTarget => Map[String, Seq[Port]] = genModelChannelPortMap(Some(Input))
|
||||
def modelOutputChannelPortMap: ModuleTarget => Map[String, Seq[Port]] = genModelChannelPortMap(Some(Output))
|
||||
def modelChannelPortMap: ModuleTarget => Map[String, Seq[Port]] = genModelChannelPortMap(None)
|
||||
def modelInputChannelPortMap: ModuleTarget => Map[String, (Option[Port], Seq[Port])] = genModelChannelPortMap(Some(Input))
|
||||
def modelOutputChannelPortMap: ModuleTarget => Map[String, (Option[Port], Seq[Port])] = genModelChannelPortMap(Some(Output))
|
||||
def modelChannelPortMap: ModuleTarget => Map[String, (Option[Port], Seq[Port])] = genModelChannelPortMap(None)
|
||||
|
||||
def getSinkHostDecoupledChannelType(cName: String): Type = {
|
||||
FAMEChannelAnalysis.getHostDecoupledChannelType(cName, sinkPorts(cName).map(portNodes(_)))
|
||||
|
@ -210,28 +226,34 @@ private[fame] class FAMEChannelAnalysis(val state: CircuitState, val fameType: F
|
|||
// - Used to produce port annotations in InferModelPorts
|
||||
// - Reran to look up port names on model instances
|
||||
class ModulePortDeduper(val mTarget: ModuleTarget) {
|
||||
val visitedLeafPort = new LinkedHashSet[Port]()
|
||||
val visitedChannel = new LinkedHashMap[Seq[Port], String]()
|
||||
val channelDedups = new LinkedHashMap[String, String]
|
||||
def channelSharesPorts(ps: Seq[Port]): Boolean = ps.map(visitedLeafPort).reduce(_ || _)
|
||||
def channelIsDuplicate(ps: Seq[Port]): Boolean = visitedChannel.contains(ps)
|
||||
|
||||
def dedupPortLists(pList: Map[String, Seq[Port]]): Map[String, Seq[Port]] = pList.flatMap({
|
||||
case (cName, Nil) => throw new RuntimeException(s"Channel ${cName} is empty (has no associate ports)")
|
||||
case (_, ports) if channelSharesPorts(ports) && !channelIsDuplicate(ports) =>
|
||||
private val visitedLeafPort = new LinkedHashSet[Port]()
|
||||
private val visitedChannel = new LinkedHashMap[(Option[Port], Seq[Port]), String]()
|
||||
|
||||
private def channelIsDuplicate(ps: (Option[Port], Seq[Port])): Boolean = visitedChannel.contains(ps)
|
||||
private def channelSharesPorts(ps: (Option[Port], Seq[Port])): Boolean = ps match {
|
||||
case (clk, ports) => ports.exists(visitedLeafPort(_)) // clock can be shared
|
||||
}
|
||||
|
||||
private def dedupPortLists(pList: Map[String, (Option[Port], Seq[Port])]): Map[String, (Option[Port], Seq[Port])] = pList.flatMap({
|
||||
case (cName, (_, Nil)) => throw new RuntimeException(s"Channel ${cName} is empty (has no associated ports)")
|
||||
case (_, clockAndPorts) if channelSharesPorts(clockAndPorts) && !channelIsDuplicate(clockAndPorts) =>
|
||||
throw new RuntimeException("Channel definition has partially overlapping ports with existing channel definition")
|
||||
case (cName, ports) if channelIsDuplicate(ports) =>
|
||||
channelDedups(cName) = visitedChannel(ports)
|
||||
case (cName, clockAndPorts) if channelIsDuplicate(clockAndPorts) =>
|
||||
channelDedups(cName) = visitedChannel(clockAndPorts)
|
||||
None
|
||||
case (cName, ports) =>
|
||||
visitedChannel(ports) = cName
|
||||
case (cName, (clock, ports)) =>
|
||||
visitedChannel((clock, ports)) = cName
|
||||
visitedLeafPort ++= clock
|
||||
visitedLeafPort ++= ports
|
||||
channelDedups(cName) = cName
|
||||
Some(cName, ports)
|
||||
Some(cName, (clock, ports))
|
||||
}).toMap
|
||||
|
||||
val inputPortMap = dedupPortLists(inputPortsByChannel(mTarget))
|
||||
val outputPortMap = dedupPortLists(outputPortsByChannel(mTarget))
|
||||
private val inputPortMap = dedupPortLists(portsByInputChannel(mTarget))
|
||||
private val outputPortMap = dedupPortLists(portsByOutputChannel(mTarget))
|
||||
|
||||
val completePortMap = inputPortMap ++ outputPortMap
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes.fame
|
||||
|
||||
import firrtl._
|
||||
import firrtl.traversals.Foreachers._
|
||||
import firrtl.ir._
|
||||
import firrtl.annotations._
|
||||
|
||||
import collection.mutable
|
||||
|
||||
/** In general, multi-clock Golden Gate simulations contain exactly one "hub" model that coordinates the clock domains
|
||||
* and has a clock channel. Before this pass runs, loopback channels (those that run from one model to another) have no
|
||||
* associated clock. The FAME transform expects that all channels that connect to the hub model have an associated
|
||||
* clock, so this pass finds the top-level clock connection that drives the single clock of each "satellite" (non-hub)
|
||||
* model and associates the clock output of the hub model driving this clock with all channels connecting the hub and
|
||||
* the satellite. Channels between satellite models are not changed.
|
||||
*/
|
||||
object FindDefaultClocks extends Transform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
|
||||
case class ModelInstance(name: String)
|
||||
case class ClockConnection(source: ReferenceTarget, sink: ReferenceTarget)
|
||||
type ClockConnMap = mutable.Map[ModelInstance, ClockConnection]
|
||||
|
||||
def recordDefaultClocks(topTarget: ModuleTarget, defaultClocks: ClockConnMap)(stmt: Statement): Unit = stmt match {
|
||||
case Connect(_, WSubField(WRef(lInst, _, InstanceKind, _), lPort, ClockType, _), WSubField(WRef(rInst, _, InstanceKind, _), rPort, ClockType, _)) =>
|
||||
// LHS must be "satellite" model receiving clock from "hub" model on RHS
|
||||
defaultClocks(ModelInstance(lInst)) = ClockConnection(topTarget.ref(rInst).field(rPort), topTarget.ref(lInst).field(lPort))
|
||||
case s => s.foreach(recordDefaultClocks(topTarget, defaultClocks))
|
||||
}
|
||||
|
||||
def addDefaultClocks(hubModel: ModelInstance, defaultClocks: ClockConnMap)(anno: Annotation): Annotation = anno match {
|
||||
case fcca @ FAMEChannelConnectionAnnotation(name, info, None, Some(sources), Some(sinks)) =>
|
||||
// unclocked loopback channel
|
||||
val sourceModelInst = ModelInstance(sources.head.ref)
|
||||
val sinkModelInst = ModelInstance(sinks.head.ref)
|
||||
if (sourceModelInst == hubModel) {
|
||||
fcca.copy(clock = Some(defaultClocks(sinkModelInst).source))
|
||||
} else if (sinkModelInst == hubModel) {
|
||||
fcca.copy(clock = Some(defaultClocks(sourceModelInst).source))
|
||||
} else {
|
||||
fcca
|
||||
}
|
||||
case a => a
|
||||
}
|
||||
|
||||
def getStmts(stmt: Statement): Seq[Statement] = stmt match {
|
||||
case Block(stmts) => stmts.flatMap(getStmts(_))
|
||||
case s => Seq(s)
|
||||
}
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
val topModule = state.circuit.modules.find(_.name == state.circuit.main).get.asInstanceOf[Module]
|
||||
val cTarget = CircuitTarget(state.circuit.main)
|
||||
val topTarget = cTarget.module(topModule.name)
|
||||
val defaultClocks = new mutable.LinkedHashMap[ModelInstance, ClockConnection]
|
||||
|
||||
// Find an arbitrary clock channel sink
|
||||
val refClock = state.annotations.collectFirst {
|
||||
case FAMEChannelConnectionAnnotation(_, TargetClockChannel(_), None, _, Some(sinks)) => sinks.head
|
||||
}
|
||||
|
||||
// Get the wrapper top port reference it points to
|
||||
val refClockPort = refClock.get.ref
|
||||
|
||||
val hubModel = getStmts(topModule.body).collectFirst {
|
||||
case Connect(_, WSubField(WRef(lInst, _, _, _), _, _, _), WRef(`refClockPort`, _, PortKind, _)) => ModelInstance(lInst)
|
||||
}
|
||||
|
||||
topModule.body.foreach(recordDefaultClocks(topTarget, defaultClocks))
|
||||
state.copy(annotations = state.annotations.map(addDefaultClocks(hubModel.get, defaultClocks)))
|
||||
}
|
||||
}
|
|
@ -17,16 +17,23 @@ class InferModelPorts extends Transform {
|
|||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
|
||||
def portAnnos(mt: ModuleTarget, cName: String, clk: Option[Port], ports: Seq[Port]): Seq[Annotation] = {
|
||||
val clkRT = clk.map(p => mt.ref(p.name))
|
||||
val portsRT = ports.map(p => mt.ref(p.name))
|
||||
val fcpa = FAMEChannelPortsAnnotation(cName, clkRT, portsRT)
|
||||
// Label all the channel ports with don't touch so as to prevent
|
||||
// annotation renaming from breaking downstream
|
||||
fcpa +: (clkRT ++: portsRT).map(DontTouchAnnotation(_))
|
||||
}
|
||||
|
||||
override def execute(state: CircuitState): CircuitState = {
|
||||
val analysis = new FAMEChannelAnalysis(state, FAME1Transform)
|
||||
val cTarget = CircuitTarget(state.circuit.main)
|
||||
val modelChannelPortsAnnos = analysis.modulePortDedupers.flatMap(deduper =>
|
||||
deduper.completePortMap.flatMap({ case (cName, ports) => Seq(
|
||||
FAMEChannelPortsAnnotation(cName, ports.map(p => deduper.mTarget.ref(p.name)))) ++
|
||||
// Label all the channel ports with don't touch so as to prevent
|
||||
// annotation renaming from breaking downstream
|
||||
ports.map(p => DontTouchAnnotation(deduper.mTarget.ref(p.name)))
|
||||
}))
|
||||
val modelChannelPortsAnnos = analysis.modulePortDedupers.flatMap {
|
||||
case deduper => deduper.completePortMap.flatMap {
|
||||
case (cName, (clk, ports)) => portAnnos(deduper.mTarget, cName, clk, ports)
|
||||
}
|
||||
}
|
||||
state.copy(annotations = state.annotations ++ modelChannelPortsAnnos)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import Mappers._
|
|||
import ir._
|
||||
import annotations._
|
||||
import collection.mutable.ArrayBuffer
|
||||
import midas.targetutils.FirrtlMemModelAnnotation
|
||||
import midas.targetutils.{FirrtlMemModelAnnotation, FirrtlFAMEModelAnnotation}
|
||||
|
||||
class LabelSRAMModels extends Transform {
|
||||
def inputForm = HighForm
|
||||
|
@ -41,7 +41,7 @@ class LabelSRAMModels extends Transform {
|
|||
val wrapper = mem2Module(mem).copy(name = moduleNS.newName(mem.name))
|
||||
val wrapperTarget = ModuleTarget(circ.main, wrapper.name)
|
||||
memModules += wrapper
|
||||
memModelAnnotations += FAMEModelAnnotation(mt.instOf(mem.name, wrapper.name))
|
||||
memModelAnnotations += FirrtlFAMEModelAnnotation(mt.instOf(mem.name, wrapper.name))
|
||||
memModelAnnotations ++= mem.readers.map(rp => ModelReadPort(wrapperTarget.ref(rp)))
|
||||
memModelAnnotations ++= mem.writers.map(rp => ModelWritePort(wrapperTarget.ref(rp)))
|
||||
memModelAnnotations ++= mem.readwriters.map(rp => ModelReadWritePort(wrapperTarget.ref(rp)))
|
||||
|
|
|
@ -0,0 +1,213 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes.fame
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.Mappers._
|
||||
import firrtl.traversals.Foreachers._
|
||||
import firrtl.annotations.ModuleTarget
|
||||
import firrtl.annotations.TargetToken.{Instance, OfModule}
|
||||
import firrtl.Utils.BoolType
|
||||
|
||||
import midas.targetutils.FirrtlEnableModelMultiThreadingAnnotation
|
||||
|
||||
import collection.mutable
|
||||
|
||||
import midas.passes._
|
||||
|
||||
// PREREQUISITE: Top-level simulator is already fully FAME-Transformed
|
||||
// ASSUMPTION: Each channel is bulk-connected to EXACTLY ONE top-level port
|
||||
// ASSUMPTION: The direction of a channel port on a model mirrors its data flow direction
|
||||
|
||||
trait ReadyValidSignal {
|
||||
val ref: Expression
|
||||
def ready: WSubField = WSubField(ref, "ready", BoolType, UNKNOWNGENDER)
|
||||
def valid: WSubField = WSubField(ref, "valid", BoolType, UNKNOWNGENDER)
|
||||
def bits: WSubField = WSubField(ref, "bits", UnknownType, UNKNOWNGENDER)
|
||||
}
|
||||
|
||||
case class ReadyValidSink(ref: Expression) extends ReadyValidSignal
|
||||
|
||||
case class ReadyValidSource(ref: Expression) extends ReadyValidSignal
|
||||
|
||||
object FAME5Info {
|
||||
def info = FileInfo(StringLit("@ [Added during FAME5Transform]"))
|
||||
}
|
||||
|
||||
object Counter {
|
||||
def apply(nVals: Integer, hostClock: Expression, hostReset: Expression)(implicit ns: Namespace): SignalInfo = {
|
||||
val maxLit = UIntLiteral(BigInt(nVals - 1))
|
||||
val decl = DefRegister(FAME5Info.info, ns.newName("threadIdx"), maxLit.tpe, hostClock, hostReset, UIntLiteral(0))
|
||||
val ref = WRef(decl)
|
||||
val inc = DoPrim(PrimOps.Add, Seq(ref, UIntLiteral(1)), Nil, UnknownType)
|
||||
val wrap = DoPrim(PrimOps.Eq, Seq(ref, maxLit), Nil, BoolType)
|
||||
val assign = Connect(FAME5Info.info, ref, Mux(wrap, UIntLiteral(0), inc, UnknownType))
|
||||
SignalInfo(decl, assign, ref)
|
||||
}
|
||||
}
|
||||
|
||||
class StaticArbiter(counter: SignalInfo) {
|
||||
private def muxIdx(select: Expression, signals: Seq[Expression]): Expression = {
|
||||
signals.zipWithIndex.tail.foldLeft(signals.head) { case (fval, (tval, idx)) =>
|
||||
Mux(DoPrim(PrimOps.Eq, Seq(select, UIntLiteral(idx)), Nil, BoolType), tval, fval, UnknownType)
|
||||
}
|
||||
}
|
||||
|
||||
private def counterMask(counter: SignalInfo, idx: Integer, signal: WSubField): DoPrim = {
|
||||
val eq = DoPrim(PrimOps.Eq, Seq(counter.ref, UIntLiteral(BigInt(idx))), Nil, BoolType)
|
||||
DoPrim(PrimOps.And, Seq(signal, eq), Nil, BoolType)
|
||||
}
|
||||
|
||||
def mux(sink: ReadyValidSink, sources: Seq[ReadyValidSource]): Statement = {
|
||||
val valid = muxIdx(counter.ref, sources.map(_.valid))
|
||||
val validConn = Connect(FAME5Info.info, sink.valid, valid)
|
||||
val bits = muxIdx(counter.ref, sources.map(_.bits))
|
||||
val bitsConn = Connect(FAME5Info.info, sink.bits, bits)
|
||||
val readyConns = sources.zipWithIndex.map {
|
||||
case (source, idx) => Connect(FAME5Info.info, source.ready, counterMask(counter, idx, sink.ready))
|
||||
}
|
||||
Block(validConn +: bitsConn +: readyConns)
|
||||
}
|
||||
|
||||
def demux(sinks: Seq[ReadyValidSink], source: ReadyValidSource): Statement = {
|
||||
val ready = muxIdx(counter.ref, sinks.map(_.ready))
|
||||
val readyConn = Connect(FAME5Info.info, source.ready, ready)
|
||||
val bitsConns = sinks.map(sink => Connect(FAME5Info.info, sink.bits, source.bits))
|
||||
val validConns = sinks.zipWithIndex.map {
|
||||
case (sink, idx) => Connect(FAME5Info.info, sink.valid, counterMask(counter, idx, source.valid))
|
||||
}
|
||||
Block(readyConn +: bitsConns ++: validConns)
|
||||
}
|
||||
}
|
||||
|
||||
object MultiThreadFAME5Models extends Transform {
|
||||
def inputForm = HighForm
|
||||
def outputForm = HighForm
|
||||
|
||||
type TopoMap = Map[OfModule, Map[String, mutable.Map[Instance, WRef]]]
|
||||
|
||||
private def analyzeAndPruneTopo(fame5InstMap: Map[Instance, OfModule], topo: TopoMap)(stmt: Statement): Statement = {
|
||||
def processChannelConn(inst: Instance, modelPort: String, topConn: WRef) = {
|
||||
if (fame5InstMap.contains(inst)) {
|
||||
topo(fame5InstMap(inst))(modelPort)(inst) = topConn
|
||||
EmptyStmt
|
||||
} else {
|
||||
stmt
|
||||
}
|
||||
}
|
||||
|
||||
stmt match {
|
||||
case Connect(_, WSubField(WRef(iName, _, InstanceKind, _), ipName, BundleType(_), _), wr: WRef) =>
|
||||
// Could infer that this is an input channel, but that would be an extra assumption
|
||||
processChannelConn(Instance(iName), ipName, wr)
|
||||
case Connect(_, wr: WRef, WSubField(WRef(iName, _, InstanceKind, _), ipName, BundleType(_), _)) =>
|
||||
// Could infer that this is an output channel, but that would be an extra assumption
|
||||
processChannelConn(Instance(iName), ipName, wr)
|
||||
case Connect(_, WSubField(WRef(iName, _, InstanceKind, _), _, _, _), _) if fame5InstMap.contains(Instance(iName)) =>
|
||||
EmptyStmt // prune non-channel connections
|
||||
case WDefInstance(_, iName, mName, _) if fame5InstMap.contains(Instance(iName)) =>
|
||||
EmptyStmt
|
||||
case s =>
|
||||
s.map(analyzeAndPruneTopo(fame5InstMap, topo))
|
||||
}
|
||||
}
|
||||
|
||||
private def findFAME5(modInsts: collection.Map[OfModule, mutable.LinkedHashSet[Instance]])(stmt: Statement): Unit = {
|
||||
stmt match {
|
||||
case WDefInstance(_, iName, mName, _) => modInsts.get(OfModule(mName)).foreach(iSet => iSet += Instance(iName))
|
||||
case s => s.foreach(findFAME5(modInsts))
|
||||
}
|
||||
}
|
||||
|
||||
override def execute(state: CircuitState): CircuitState = {
|
||||
val moduleDefs = state.circuit.modules.collect({ case m: Module => OfModule(m.name) -> m}).toMap
|
||||
|
||||
val top = moduleDefs(OfModule(state.circuit.main))
|
||||
implicit val ns = Namespace(top)
|
||||
|
||||
val hostClock = WRef(top.ports.find(_.name == WrapTop.hostClockName).get)
|
||||
val hostReset = WRef(top.ports.find(_.name == WrapTop.hostResetName).get)
|
||||
|
||||
// Populate keys from annotations, values from traversing statements
|
||||
val fame5RawInstances = new mutable.LinkedHashMap[OfModule, mutable.LinkedHashSet[Instance]]
|
||||
state.annotations.foreach {
|
||||
case FirrtlEnableModelMultiThreadingAnnotation(ModuleTarget(_, m)) =>
|
||||
fame5RawInstances(OfModule(m)) = new mutable.LinkedHashSet[Instance]
|
||||
case _ =>
|
||||
}
|
||||
|
||||
top.body.foreach(findFAME5(fame5RawInstances))
|
||||
|
||||
// filter models with one instance to avoid needless multithreading
|
||||
val fame5InstancesByModule = fame5RawInstances.filter { case (k, v) => v.size > 1 }
|
||||
val fame5ModulesByInstance = fame5InstancesByModule.flatMap({ case (k, v) => v.map(vv => vv -> k) }).toMap
|
||||
|
||||
// Maps from an (OfModule, PortName) pair
|
||||
// It's actually nested (rather than indexed by tuple) for convenience
|
||||
// We don't track the direction in this map, since we can just find it later
|
||||
val fame5Topo: TopoMap = fame5InstancesByModule.map({
|
||||
case (m, iSet) => m -> moduleDefs(m).ports.collect({
|
||||
case Port(_, name, _, BundleType(_)) => name -> new mutable.HashMap[Instance, WRef]
|
||||
}).toMap
|
||||
}).toMap
|
||||
|
||||
val prunedTopoTopBody = top.body.map(analyzeAndPruneTopo(fame5ModulesByInstance, fame5Topo))
|
||||
|
||||
// For now, just one FAME5 model
|
||||
assert(fame5InstancesByModule.keySet.size <= 1)
|
||||
|
||||
val nThreads = fame5InstancesByModule.headOption.map(_._2.size).getOrElse(1)
|
||||
|
||||
val circuitNS = Namespace(state.circuit)
|
||||
val threadedModuleNames = state.circuit.modules.collect({
|
||||
// Don't replace blackbox instances! TODO: Check for illegal blackboxes.
|
||||
case m: Module => m.name -> circuitNS.newName(s"${m.name}_threaded")
|
||||
}).toMap
|
||||
|
||||
val threadedInstances = fame5InstancesByModule.map({
|
||||
case (m, insts) => m -> WDefInstance(FAME5Info.info, ns.newName(s"${m.value}_threaded"), threadedModuleNames(m.value), UnknownType)
|
||||
}).toMap
|
||||
|
||||
val threadCounters = fame5InstancesByModule.map { case (m, insts) => m -> Counter(insts.size, hostClock, hostReset) }
|
||||
|
||||
val multiThreadedConns: Seq[Statement] = fame5Topo.toSeq.flatMap {
|
||||
case (mod, connsByPort) =>
|
||||
val arbiter = new StaticArbiter(threadCounters(mod))
|
||||
connsByPort.toSeq.map {
|
||||
case (port, connsByInstance) =>
|
||||
val canonicalOrderedConns = fame5InstancesByModule(mod).map(inst => connsByInstance(inst)).toSeq
|
||||
if (moduleDefs(mod).ports.exists(p => p.name == port && p.direction == Input)) {
|
||||
val sink = ReadyValidSink(WSubField(WRef(threadedInstances(mod)), port))
|
||||
val sources = canonicalOrderedConns.map(s => ReadyValidSource(s))
|
||||
arbiter.mux(sink, sources)
|
||||
} else {
|
||||
val sinks = canonicalOrderedConns.map(s => ReadyValidSink(s))
|
||||
val source = ReadyValidSource(WSubField(WRef(threadedInstances(mod)), port))
|
||||
arbiter.demux(sinks, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val insts = threadedInstances.toSeq.map { case (k, v) => v } // keep ordering
|
||||
val counters = threadCounters.toSeq.map { case (k, v) => v } // keep ordering
|
||||
val clockConns = insts.map(i => Connect(FAME5Info.info, WSubField(WRef(i), WrapTop.hostClockName), WRef(WrapTop.hostClockName)))
|
||||
val resetConns = insts.map(i => Connect(FAME5Info.info, WSubField(WRef(i), WrapTop.hostResetName), WRef(WrapTop.hostResetName)))
|
||||
|
||||
val prologue = insts ++: clockConns ++: resetConns ++: counters.flatMap(c => Seq(c.decl, c.assigns))
|
||||
val multiThreadedTopBody = Block(prologue ++: prunedTopoTopBody +: multiThreadedConns)
|
||||
|
||||
val transformedModules = state.circuit.modules.flatMap {
|
||||
case m: Module if (m.name == state.circuit.main) =>
|
||||
Seq(m.copy(body = multiThreadedTopBody))
|
||||
case m: Module =>
|
||||
val threaded = MuxingMultiThreader(threadedModuleNames)(m, nThreads) // all threaded by same amount, many get pruned
|
||||
Seq(m, threaded)
|
||||
case m => Seq(m)
|
||||
}
|
||||
|
||||
// TODO: Renames!
|
||||
|
||||
state.copy(circuit = state.circuit.copy(modules = transformedModules))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes.fame
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.Mappers._
|
||||
import firrtl.traversals.Foreachers._
|
||||
import firrtl.Utils.{BoolType, kind, zero, one}
|
||||
|
||||
import midas.passes.SignalInfo
|
||||
|
||||
import collection.mutable
|
||||
import collection.immutable.HashMap
|
||||
|
||||
/* TODO:
|
||||
*
|
||||
* This got really messy because of the need to emulate gated
|
||||
* clocks. This involves recovering the enable from the gated clock
|
||||
* and using it to select either the output of the logic or the
|
||||
* shadowed previous value of the currently active register slot.
|
||||
*
|
||||
* Also TODO: maybe use fewer than 200 characters per line.
|
||||
*/
|
||||
|
||||
// Utility to help "float" instance declarations to the top of a block for convenience
|
||||
object SeparateInstanceDecls {
|
||||
private def onStmt(insts: mutable.ArrayBuffer[WDefInstance])(stmt: Statement): Statement = stmt match {
|
||||
case wi: WDefInstance =>
|
||||
insts.append(wi)
|
||||
EmptyStmt
|
||||
case s => s.map(onStmt(insts))
|
||||
}
|
||||
|
||||
def apply(stmt: Statement): (Seq[WDefInstance], Statement) = {
|
||||
val insts = new mutable.ArrayBuffer[WDefInstance]
|
||||
val otherStmts = onStmt(insts)(stmt)
|
||||
(insts.toSeq, otherStmts)
|
||||
}
|
||||
}
|
||||
|
||||
object AddHostClockAndReset {
|
||||
val hostClock = Port(FAME5Info.info, WrapTop.hostClockName, Input, ClockType)
|
||||
val hostReset = Port(FAME5Info.info, WrapTop.hostResetName, Input, BoolType)
|
||||
|
||||
def apply(m: Module): Module = {
|
||||
m.copy(ports = m.ports ++ Seq(hostClock, hostReset).filterNot(p => m.ports.map(_.name).contains(p.name)))
|
||||
}
|
||||
|
||||
def apply(wi: WDefInstance): Statement = {
|
||||
// Adds connections to instance hostClock/hostReset ports
|
||||
val hcConn = Connect(FAME5Info.info, WSubField(WRef(wi), WrapTop.hostClockName), WRef(WrapTop.hostClockName))
|
||||
val hrConn = Connect(FAME5Info.info, WSubField(WRef(wi), WrapTop.hostResetName), WRef(WrapTop.hostResetName))
|
||||
Block(Seq(wi, hcConn, hrConn))
|
||||
}
|
||||
}
|
||||
|
||||
object Toggle {
|
||||
def apply(r: DefRegister): Statement = {
|
||||
Connect(FAME5Info.info, WRef(r), DoPrim(PrimOps.Not, Seq(WRef(r)), Nil, r.tpe))
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidates WIR _.tpe (types of memories change)
|
||||
object MultiThreader {
|
||||
|
||||
type FreshNames = Map[String, Seq[String]]
|
||||
|
||||
def renameRegs(freshNames: FreshNames, n: BigInt, ns: Namespace, stmt: Statement): FreshNames = stmt match {
|
||||
case Block(stmts) =>
|
||||
stmts.foldLeft(freshNames) { case (rm, s) => renameRegs(rm, n, ns, s) }
|
||||
case Conditionally(_, _, cons, alt) =>
|
||||
renameRegs(renameRegs(freshNames, n, ns, cons), n, ns, alt)
|
||||
case reg: DefRegister =>
|
||||
// One extra register to shadow value for clock-gated registers
|
||||
val regNames = (0 to n.intValue()).map(i => ns.newName(s"${reg.name}_slot_${i}"))
|
||||
freshNames.updated(reg.name, regNames)
|
||||
case s => freshNames
|
||||
}
|
||||
|
||||
def replaceRegRefsLHS(freshNames: FreshNames)(expr: Expression): Expression = expr match {
|
||||
case wr @ WRef(name, _, RegKind, _) => wr.copy(name = freshNames(name).head)
|
||||
case e => e.map(replaceRegRefsLHS(freshNames))
|
||||
}
|
||||
|
||||
def replaceRegRefsRHS(freshNames: FreshNames)(expr: Expression): Expression = expr match {
|
||||
// 2nd-to-last slot feeds logic; last slot holds a shadow value
|
||||
case wr @ WRef(name, _, RegKind, _) => wr.copy(name = freshNames(name).init.last)
|
||||
case e => e.map(replaceRegRefsRHS(freshNames))
|
||||
}
|
||||
|
||||
def replaceRegRefs(freshNames: FreshNames)(expr: Expression): Expression = expr match {
|
||||
case wr @ WRef(name, _, RegKind, SinkFlow) => wr.copy(name = freshNames(name).head)
|
||||
// 2nd-to-last slot feeds logic; last slot holds a shadow value
|
||||
case wr @ WRef(name, _, RegKind, SourceFlow) => wr.copy(name = freshNames(name).init.last)
|
||||
case e => e.map(replaceRegRefs(freshNames))
|
||||
}
|
||||
|
||||
def transformDepth(depth: BigInt, n: BigInt): BigInt = {
|
||||
require(n.bitCount == 1) // pow2 threads for now
|
||||
depth * n
|
||||
}
|
||||
|
||||
def transformAddr(counter: DefRegister, expr: Expression): Expression = {
|
||||
DoPrim(PrimOps.Cat, Seq(expr, WRef(counter)), Nil, UnknownType)
|
||||
}
|
||||
|
||||
def updateReg(reg: DefRegister, slotName: String): DefRegister = {
|
||||
// Keep self-resets as self-resets
|
||||
val newInit = reg.init match {
|
||||
case wr: WRef if (wr.name == reg.name) => wr.copy(name = slotName)
|
||||
case e => e
|
||||
}
|
||||
// All slot registers are free-running
|
||||
reg.copy(name = slotName, init = newInit, clock = WRef(WrapTop.hostClockName))
|
||||
}
|
||||
|
||||
def multiThread(freshNames: FreshNames, edgeStatus: collection.Map[WrappedExpression, SignalInfo], n: BigInt, counter: DefRegister)(stmt: Statement): Statement = {
|
||||
stmt match {
|
||||
case mem: DefMemory =>
|
||||
assert(mem.readLatency == 0 && mem.writeLatency == 1, "Memories must be transformed with VerilogMemDelays before multithreading")
|
||||
mem.copy(depth = transformDepth(mem.depth, n), readLatency = mem.readLatency * n.intValue())
|
||||
case reg: DefRegister =>
|
||||
val newRegs: Seq[DefRegister] = freshNames(reg.name).map(alias => updateReg(reg, alias))
|
||||
// Muxing happens between first two stages
|
||||
val useNew = edgeStatus(WrappedExpression(reg.clock)).ref
|
||||
val gatedUpdate = Connect(FAME5Info.info, WRef(newRegs.tail.head), Mux(useNew, WRef(newRegs.head), WRef(newRegs.last), UnknownType))
|
||||
// Other stages are straight connections
|
||||
val directPairs = newRegs.tail zip newRegs.tail.tail
|
||||
val directConns = directPairs.map { case (a, b) => Connect(FAME5Info.info, WRef(b), WRef(a)) }
|
||||
Block(newRegs ++: gatedUpdate +: directConns)
|
||||
case Connect(info, lhs @ WSubField(p: WSubField, "addr", _, _), rhs) if kind(lhs) == MemKind =>
|
||||
Connect(info, replaceRegRefsLHS(freshNames)(lhs), transformAddr(counter, replaceRegRefsRHS(freshNames)(rhs)))
|
||||
case Connect(info, lhs, rhs) =>
|
||||
// TODO: LHS vs RHS is kind of a hack
|
||||
// We need a new method to swap register refs on the LHS because VerilogMemDelays puts in un-gendered register refs
|
||||
Connect(info, replaceRegRefsLHS(freshNames)(lhs), replaceRegRefsRHS(freshNames)(rhs))
|
||||
case s => s.map(multiThread(freshNames, edgeStatus, n, counter)).map(replaceRegRefsRHS(freshNames))
|
||||
}
|
||||
}
|
||||
|
||||
def findClocks(clocks: mutable.Set[WrappedExpression])(stmt: Statement): Unit = {
|
||||
def findClocksExpr(expr: Expression): Unit = {
|
||||
if (expr.tpe == ClockType && Utils.gender(expr) == MALE) {
|
||||
clocks += WrappedExpression(expr)
|
||||
}
|
||||
expr.foreach(findClocksExpr)
|
||||
}
|
||||
|
||||
stmt.foreach(findClocksExpr)
|
||||
stmt.foreach(findClocks(clocks))
|
||||
}
|
||||
|
||||
def apply(threadedModuleNames: Map[String, String])(module: Module, n: BigInt): Module = {
|
||||
// TODO: this is ugly and uses copied code instead of bumping FIRRTL
|
||||
// Simplify all memories first
|
||||
val loweredMod = (new MemDelayAndReadwriteTransformer(module)).transformed.asInstanceOf[Module]
|
||||
|
||||
val ns = Namespace(loweredMod)
|
||||
val hostClock = WRef(WrapTop.hostClockName)
|
||||
val hostReset = WRef(WrapTop.hostResetName)
|
||||
|
||||
val clocks = new mutable.LinkedHashSet[WrappedExpression]
|
||||
loweredMod.body.foreach(findClocks(clocks))
|
||||
|
||||
val edgeStatus = new mutable.LinkedHashMap[WrappedExpression, SignalInfo]
|
||||
clocks.foreach {
|
||||
case we => we.e1 match {
|
||||
case WRef(WrapTop.hostClockName, _, _, _) =>
|
||||
// Optimization -- don't generate this gate recovery stuff for host clock
|
||||
edgeStatus(we) = SignalInfo(EmptyStmt, EmptyStmt, UIntLiteral(1))
|
||||
case e =>
|
||||
val edgeCount = DefRegister(FAME5Info.info, ns.newName("edgeCount"), BoolType, e, hostReset, UIntLiteral(0))
|
||||
val updateCount = DefRegister(FAME5Info.info, ns.newName("updateCount"), BoolType, hostClock, hostReset, UIntLiteral(0))
|
||||
val neq = DoPrim(PrimOps.Neq, Seq(WRef(edgeCount), WRef(updateCount)), Nil, BoolType)
|
||||
val trackUpdates = Conditionally(FAME5Info.info, neq, Toggle(updateCount), EmptyStmt)
|
||||
edgeStatus(we) = SignalInfo(Block(Seq(edgeCount, updateCount)), Block(Seq(Toggle(edgeCount), trackUpdates)), neq)
|
||||
}
|
||||
}
|
||||
|
||||
val tidxMax = UIntLiteral(n-1)
|
||||
val tidxType = UIntType(tidxMax.width)
|
||||
val tidxRef = WRef(ns.newName("threadIdx"), tidxType, RegKind)
|
||||
val tidxDecl = DefRegister(FAME5Info.info, tidxRef.name, tidxType, hostClock, zero, tidxRef)
|
||||
val tidxUpdate = Mux(
|
||||
DoPrim(PrimOps.Eq, Seq(tidxRef, tidxMax), Nil, BoolType),
|
||||
UIntLiteral(0),
|
||||
DoPrim(PrimOps.Add, Seq(tidxRef, one), Nil, BoolType),
|
||||
tidxType)
|
||||
val tidxConn = Connect(FAME5Info.info, tidxRef, tidxUpdate)
|
||||
|
||||
val freshNames = renameRegs(HashMap.empty, n, ns, loweredMod.body)
|
||||
val threaded = multiThread(freshNames, edgeStatus, n, tidxDecl)(loweredMod.body)
|
||||
|
||||
// Uses only threaded instances
|
||||
val (iDecls, body) = SeparateInstanceDecls(threaded)
|
||||
|
||||
val threadedChildren = iDecls.map {
|
||||
case i if (threadedModuleNames.contains(i.module)) =>
|
||||
AddHostClockAndReset(i.copy(module = threadedModuleNames(i.module)))
|
||||
case i => i
|
||||
}
|
||||
|
||||
val clockGaters = edgeStatus.toSeq.map { case (k, v) => v }
|
||||
val threadedBody = Block(threadedChildren ++ clockGaters.map(_.decl) ++ Seq(tidxDecl, tidxConn, body) ++ clockGaters.map(_.assigns))
|
||||
|
||||
val hostPorts = Seq(Port(FAME5Info.info, hostClock.name, Input, ClockType), Port(FAME5Info.info, hostReset.name, Input, BoolType))
|
||||
val newPorts = module.ports ++ hostPorts.filterNot(p => module.ports.map(_.name).contains(p.name))
|
||||
Module(FAME5Info.info ++ module.info, threadedModuleNames(module.name), newPorts, threadedBody)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes.fame
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.Mappers._
|
||||
import firrtl.passes.MemPortUtils._
|
||||
import firrtl.traversals.Foreachers._
|
||||
import firrtl.Utils.{BoolType, kind}
|
||||
|
||||
import collection.mutable.ArrayBuffer
|
||||
|
||||
// Invalidates WIR _.tpe (types of memories change)
|
||||
object MuxingMultiThreader {
|
||||
|
||||
type FreshNames = Map[String, Seq[String]]
|
||||
|
||||
val rPortName = "read"
|
||||
val wPortName = "write"
|
||||
|
||||
def rField(mem: DefMemory, field: String): Expression = memPortField(mem, rPortName, field)
|
||||
def wField(mem: DefMemory, field: String): Expression = memPortField(mem, wPortName, field)
|
||||
|
||||
def onExprRHS(expr: Expression): Expression = expr match {
|
||||
case WRef(name, tpe, RegKind, _) =>
|
||||
WSubField(WSubField(WRef(name, tpe, MemKind), rPortName), "data")
|
||||
case e => e.map(onExprRHS)
|
||||
}
|
||||
|
||||
def regWriteAsMemWrite(info: Info, name: String, tpe: Type, rhs: Expression): Statement = {
|
||||
val infos = FAME5Info.info ++ info
|
||||
val wPort = WSubField(WRef(name), wPortName)
|
||||
val dataConnect = Connect(infos, WSubField(wPort, "data"), rhs)
|
||||
val enConnect = Connect(infos, WSubField(wPort, "en"), UIntLiteral(1))
|
||||
Block(Seq(dataConnect, enConnect))
|
||||
}
|
||||
|
||||
def onStmt(newResets: ArrayBuffer[Statement], nThreads: BigInt, tIdx: Expression)(stmt: Statement): Statement = stmt match {
|
||||
case DefRegister(info, name, tpe, clock, reset, init) =>
|
||||
val infos = FAME5Info.info ++ info
|
||||
val mem = DefMemory(infos, name, tpe, nThreads, 1, 0, Seq(rPortName), Seq(wPortName), Nil)
|
||||
val rClockConn = Connect(infos, rField(mem, "clk"), clock)
|
||||
val rEnConn = Connect(infos, rField(mem, "en"), UIntLiteral(1))
|
||||
val rAddrConn = Connect(infos, rField(mem, "addr"), tIdx)
|
||||
val wClockConn = Connect(infos, wField(mem, "clk"), clock)
|
||||
val wEnDefault = Connect(infos, wField(mem, "en"), UIntLiteral(0))
|
||||
val wMaskConn = Connect(infos, wField(mem, "mask"), UIntLiteral(1))
|
||||
val wAddrConn = Connect(infos, wField(mem, "addr"), tIdx)
|
||||
if (WrappedExpression(init) != WrappedExpression(WRef(name))) {
|
||||
val doReset = regWriteAsMemWrite(info, name, tpe, onExprRHS(init))
|
||||
newResets += Conditionally(infos, reset, doReset, EmptyStmt)
|
||||
}
|
||||
Block(Seq(mem, rClockConn, rEnConn, rAddrConn, wClockConn, wEnDefault, wMaskConn, wAddrConn))
|
||||
case Connect(info, lhs @ WSubField(p: WSubField, "addr", _, _), rhs) if kind(lhs) == MemKind =>
|
||||
Connect(FAME5Info.info ++ info, lhs, DoPrim(PrimOps.Cat, Seq(onExprRHS(rhs), tIdx), Nil, UnknownType))
|
||||
case Connect(info, WRef(name, tpe, RegKind, _), rhs) =>
|
||||
regWriteAsMemWrite(info, name, tpe, onExprRHS(rhs))
|
||||
case Connect(_, lhs, _) if (kind(lhs) == RegKind) =>
|
||||
throw CustomTransformException(new IllegalArgumentException(s"Cannot handle complex register assignment to ${lhs}"))
|
||||
case mem: DefMemory =>
|
||||
require(mem.readLatency == 0, "Memories must be transformed with VerilogMemDelays before multithreading")
|
||||
require(mem.readLatency == 0, "Memories must have one-cycle write latency")
|
||||
require(nThreads.bitCount == 1, "Models may only be threaded by pow2 threads for now")
|
||||
mem.copy(depth = mem.depth * nThreads)
|
||||
case s => s.map(onStmt(newResets, nThreads, tIdx)).map(onExprRHS)
|
||||
}
|
||||
|
||||
def apply(threadedModuleNames: Map[String, String])(module: Module, n: BigInt): Module = {
|
||||
// TODO: this is ugly and uses copied code instead of bumping FIRRTL
|
||||
// Simplify all memories first
|
||||
val loweredMod = (new MemDelayAndReadwriteTransformer(module)).transformed.asInstanceOf[Module]
|
||||
val ns = Namespace(loweredMod)
|
||||
|
||||
val hostClock = WRef(WrapTop.hostClockName)
|
||||
val hostReset = WRef(WrapTop.hostResetName)
|
||||
|
||||
val tIdxMax = UIntLiteral(n-1)
|
||||
val tIdxType = UIntType(tIdxMax.width)
|
||||
val tIdxRef = WRef(ns.newName("threadIdx"), tIdxType, RegKind)
|
||||
val tIdxDecl = DefRegister(FAME5Info.info, tIdxRef.name, tIdxType, hostClock, UIntLiteral(0), tIdxRef)
|
||||
val tIdxUpdate = Mux(
|
||||
DoPrim(PrimOps.Eq, Seq(tIdxRef, tIdxMax), Nil, BoolType),
|
||||
UIntLiteral(0),
|
||||
DoPrim(PrimOps.Add, Seq(tIdxRef, UIntLiteral(1)), Nil, BoolType),
|
||||
tIdxType)
|
||||
val tIdxConn = Connect(FAME5Info.info, tIdxRef, tIdxUpdate)
|
||||
|
||||
// Resets transformed to conditional stores to threading RAMs
|
||||
val newResets = new ArrayBuffer[Statement]
|
||||
val threaded = onStmt(newResets, n, tIdxRef)(loweredMod.body)
|
||||
|
||||
// Uses only threaded instances
|
||||
val (iDecls, threadedImpl) = SeparateInstanceDecls(threaded)
|
||||
|
||||
// TODO: earlier in the compiler, every module should get hostClock/hostReset ports hooked up
|
||||
val threadedChildren = iDecls.map {
|
||||
case i if (threadedModuleNames.contains(i.module)) =>
|
||||
AddHostClockAndReset(i.copy(module = threadedModuleNames(i.module)))
|
||||
case i => i
|
||||
}
|
||||
|
||||
val threadedBody = Block(threadedChildren ++: tIdxDecl +: tIdxConn +: threadedImpl +: newResets.toSeq)
|
||||
AddHostClockAndReset(Module(FAME5Info.info ++ module.info, threadedModuleNames(module.name), module.ports, threadedBody))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes.fame
|
||||
|
||||
import java.io.{PrintWriter, File}
|
||||
|
||||
import firrtl._
|
||||
import ir._
|
||||
import Mappers._
|
||||
import firrtl.Utils.{BoolType, kind, ceilLog2, one}
|
||||
import firrtl.passes.MemPortUtils
|
||||
import annotations._
|
||||
import scala.collection.mutable
|
||||
import mutable.{LinkedHashSet, LinkedHashMap}
|
||||
|
||||
import midas.passes._
|
||||
|
||||
object PatientMemTransformer {
|
||||
def apply(mem: DefMemory, finishing: Expression, memClock: WRef, ns: Namespace): Block = {
|
||||
val shim = DefWire(NoInfo, mem.name, MemPortUtils.memType(mem))
|
||||
val newMem = mem.copy(name = ns.newName(mem.name))
|
||||
val defaultConnect = Connect(NoInfo, WRef(shim), WRef(newMem.name, shim.tpe, MemKind))
|
||||
val syncReadPorts = (newMem.readers ++ newMem.readwriters).filter(rp => mem.readLatency > 0)
|
||||
val preserveReads = syncReadPorts.flatMap {
|
||||
case rpName =>
|
||||
val addrWidth = IntWidth(ceilLog2(mem.depth) max 1)
|
||||
val dummyReset = DefWire(NoInfo, ns.newName(s"${mem.name}_${rpName}_dummyReset"), BoolType)
|
||||
val tieOff = Connect(NoInfo, WRef(dummyReset), UIntLiteral(0))
|
||||
val addrReg = new DefRegister(NoInfo, ns.newName(s"${mem.name}_${rpName}"),
|
||||
UIntType(addrWidth), memClock, WRef(dummyReset), UIntLiteral(0, addrWidth))
|
||||
val updateReg = Connect(NoInfo, WRef(addrReg), WSubField(WSubField(WRef(shim), rpName), "addr"))
|
||||
val useReg = Connect(NoInfo, MemPortUtils.memPortField(newMem, rpName, "addr"), WRef(addrReg))
|
||||
Seq(dummyReset, tieOff, addrReg, Conditionally(NoInfo, finishing, updateReg, useReg))
|
||||
}
|
||||
val gateWrites = (newMem.writers ++ newMem.readwriters).map {
|
||||
case wpName =>
|
||||
Conditionally(
|
||||
NoInfo,
|
||||
Negate(finishing),
|
||||
Connect(NoInfo, MemPortUtils.memPortField(newMem, wpName, "en"), UIntLiteral(0, IntWidth(1))),
|
||||
EmptyStmt)
|
||||
}
|
||||
new Block(Seq(shim, newMem, defaultConnect) ++ preserveReads ++ gateWrites)
|
||||
}
|
||||
}
|
||||
|
||||
object PatientSSMTransformer {
|
||||
def apply(m: Module, analysis: FAMEChannelAnalysis)(implicit triggerName: String): Module = {
|
||||
val ns = Namespace(m)
|
||||
val clocks = m.ports.filter(_.tpe == ClockType)
|
||||
// TODO: turn this back on
|
||||
// assert(clocks.length == 1)
|
||||
val finishing = new Port(NoInfo, ns.newName(triggerName), Input, BoolType)
|
||||
val hostClock = clocks.find(_.name == "clock").getOrElse(clocks.head) // TODO: naming convention for host clock
|
||||
def onStmt(stmt: Statement): Statement = stmt.map(onStmt) match {
|
||||
case conn @ Connect(info, lhs, _) if (kind(lhs) == RegKind) =>
|
||||
Conditionally(info, WRef(finishing), conn, EmptyStmt)
|
||||
case s: Stop => s.copy(en = DoPrim(PrimOps.And, Seq(WRef(finishing), s.en), Seq.empty, BoolType))
|
||||
case p: Print => p.copy(en = DoPrim(PrimOps.And, Seq(WRef(finishing), p.en), Seq.empty, BoolType))
|
||||
case mem: DefMemory => PatientMemTransformer(mem, WRef(finishing), WRef(hostClock), ns)
|
||||
case wi: WDefInstance if analysis.syncNativeModules.contains(analysis.moduleTarget(wi)) =>
|
||||
new Block(Seq(wi, Connect(wi.info, WSubField(WRef(wi), triggerName), WRef(finishing))))
|
||||
case s => s
|
||||
}
|
||||
Module(m.info, m.name, m.ports :+ finishing, m.body.map(onStmt))
|
||||
}
|
||||
}
|
|
@ -48,11 +48,31 @@ object Negate {
|
|||
def apply(arg: Expression): Expression = DoPrim(PrimOps.Not, Seq(arg), Seq.empty, arg.tpe)
|
||||
}
|
||||
|
||||
object Reduce {
|
||||
private def _reduce(op: PrimOp, args: Iterable[Expression]): Expression = {
|
||||
args.tail.foldLeft(args.head){ (l, r) => DoPrim(op, Seq(l, r), Seq.empty, UIntType(IntWidth(1))) }
|
||||
sealed trait BinaryBooleanOp {
|
||||
def op: PrimOp
|
||||
def apply(l: Expression, r: Expression): DoPrim = DoPrim(op, Seq(l, r), Nil, BoolType)
|
||||
def reduce(args: Iterable[Expression]): Expression = {
|
||||
require(args.nonEmpty)
|
||||
args.tail.foldLeft(args.head){ (l, r) => apply(l, r) }
|
||||
}
|
||||
def and(args: Iterable[Expression]): Expression = _reduce(PrimOps.And, args)
|
||||
def or(args: Iterable[Expression]): Expression = _reduce(PrimOps.Or, args)
|
||||
}
|
||||
|
||||
object And extends BinaryBooleanOp {
|
||||
val op = PrimOps.And
|
||||
}
|
||||
|
||||
object Or extends BinaryBooleanOp {
|
||||
val op = PrimOps.Or
|
||||
}
|
||||
|
||||
object Neq extends BinaryBooleanOp {
|
||||
val op = PrimOps.Neq
|
||||
}
|
||||
|
||||
/** Generates a DefRegister with no reset, relying instead on FPGA programming
|
||||
* to preset the register to 0
|
||||
*/
|
||||
object RegZeroPreset {
|
||||
def apply(info: Info, name: String, tpe: Type, clock: Expression): DefRegister =
|
||||
DefRegister(info, name, tpe, clock, zero, WRef(name))
|
||||
}
|
||||
|
|
|
@ -34,9 +34,9 @@ class TrivialChannelExcision extends Transform {
|
|||
val fame1Anno = FAMETransformAnnotation(FAME1Transform, ModuleTarget(topName, topChildren.head.module))
|
||||
val fameChannelAnnos = topModule.ports.collect({
|
||||
case ip @ Port(_, name, Input, tpe) if !specialSignals.contains(name) =>
|
||||
FAMEChannelConnectionAnnotation(name, WireChannel, None, Some(Seq(ReferenceTarget(topName, topName, Nil, name, Nil))))
|
||||
FAMEChannelConnectionAnnotation.implicitlyClockedSink(name, WireChannel, Seq(ReferenceTarget(topName, topName, Nil, name, Nil)))
|
||||
case op @ Port(_, name, Output, tpe) =>
|
||||
FAMEChannelConnectionAnnotation(name, WireChannel, Some(Seq(ReferenceTarget(topName, topName, Nil, name, Nil))), None)
|
||||
FAMEChannelConnectionAnnotation.implicitlyClockedSource(name, WireChannel, Seq(ReferenceTarget(topName, topName, Nil, name, Nil)))
|
||||
})
|
||||
state.copy(annotations = state.annotations ++ Seq(fame1Anno) ++ fameChannelAnnos)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.passes.fame
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.Utils._
|
||||
import firrtl.Mappers._
|
||||
import firrtl.WrappedExpression._
|
||||
import firrtl.traversals.Foreachers._
|
||||
import firrtl.passes.MemPortUtils._
|
||||
|
||||
import collection.mutable
|
||||
|
||||
object MemDelayAndReadwriteTransformer {
|
||||
// Representation of a group of signals and associated valid signals
|
||||
case class WithValid(valid: Expression, payload: Seq[Expression])
|
||||
|
||||
// Grouped statements that are split into declarations and connects to ease ordering
|
||||
case class SplitStatements(decls: Seq[Statement], conns: Seq[Connect])
|
||||
|
||||
// Utilities for generating hardware
|
||||
def NOT(e: Expression) = DoPrim(PrimOps.Not, Seq(e), Nil, BoolType)
|
||||
def AND(e1: Expression, e2: Expression) = DoPrim(PrimOps.And, Seq(e1, e2), Nil, BoolType)
|
||||
def connect(l: Expression, r: Expression): Connect = Connect(NoInfo, l, r)
|
||||
def condConnect(c: Expression)(l: Expression, r: Expression): Connect = connect(l, Mux(c, r, l, l.tpe))
|
||||
|
||||
// Utilities for working with WithValid groups
|
||||
def connect(l: WithValid, r: WithValid): Seq[Connect] = {
|
||||
val paired = (l.valid +: l.payload) zip (r.valid +: r.payload)
|
||||
paired.map { case (le, re) => connect(le, re) }
|
||||
}
|
||||
|
||||
def condConnect(l: WithValid, r: WithValid): Seq[Connect] = {
|
||||
connect(l.valid, r.valid) +: (l.payload zip r.payload).map { case (le, re) => condConnect(r.valid)(le, re) }
|
||||
}
|
||||
|
||||
// Internal representation of a pipeline stage with an associated valid signal
|
||||
private case class PipeStageWithValid(idx: Int, ref: WithValid, stmts: SplitStatements = SplitStatements(Nil, Nil))
|
||||
|
||||
// Utilities for creating legal names for registers
|
||||
private val metaChars = raw"[\[\]\.]".r
|
||||
private def flatName(e: Expression) = metaChars.replaceAllIn(e.serialize, "_")
|
||||
|
||||
// Pipeline a group of signals with an associated valid signal. Gate registers when possible.
|
||||
def pipelineWithValid(ns: Namespace)(
|
||||
clock: Expression,
|
||||
depth: Int,
|
||||
src: WithValid,
|
||||
nameTemplate: Option[WithValid] = None): (WithValid, Seq[Statement], Seq[Connect]) = {
|
||||
|
||||
def asReg(e: Expression) = DefRegister(NoInfo, e.serialize, e.tpe, clock, zero, e)
|
||||
val template = nameTemplate.getOrElse(src)
|
||||
|
||||
val stages = Seq.iterate(PipeStageWithValid(0, src), depth + 1) { case prev =>
|
||||
def pipeRegRef(e: Expression) = WRef(ns.newName(s"${flatName(e)}_pipe_${prev.idx}"), e.tpe, RegKind)
|
||||
val ref = WithValid(pipeRegRef(template.valid), template.payload.map(pipeRegRef))
|
||||
val regs = (ref.valid +: ref.payload).map(asReg)
|
||||
PipeStageWithValid(prev.idx + 1, ref, SplitStatements(regs, condConnect(ref, prev.ref)))
|
||||
}
|
||||
(stages.last.ref, stages.flatMap(_.stmts.decls), stages.flatMap(_.stmts.conns))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class performs the primary work of the transform: splitting readwrite ports into separate
|
||||
* read and write ports while simultaneously compiling memory latencies to combinational-read
|
||||
* memories with delay pipelines. It is represented as a class that takes a module as a constructor
|
||||
* argument, as it encapsulates the mutable state required to analyze and transform one module.
|
||||
*
|
||||
* @note The final transformed module is found in the (sole public) field [[transformed]]
|
||||
*/
|
||||
class MemDelayAndReadwriteTransformer(m: DefModule) {
|
||||
import MemDelayAndReadwriteTransformer._
|
||||
|
||||
private val ns = Namespace(m)
|
||||
private val netlist = new collection.mutable.HashMap[WrappedExpression, Expression]
|
||||
private val exprReplacements = new collection.mutable.HashMap[WrappedExpression, Expression]
|
||||
private val newConns = new mutable.ArrayBuffer[Connect]
|
||||
|
||||
private def findMemConns(s: Statement): Unit = s match {
|
||||
case Connect(_, loc, expr) if (kind(loc) == MemKind) => netlist(we(loc)) = expr
|
||||
case _ => s.foreach(findMemConns)
|
||||
}
|
||||
|
||||
private def swapMemRefs(e: Expression): Expression = e map swapMemRefs match {
|
||||
case sf: WSubField => exprReplacements.getOrElse(we(sf), sf)
|
||||
case ex => ex
|
||||
}
|
||||
|
||||
private def transform(s: Statement): Statement = s.map(transform) match {
|
||||
case mem: DefMemory =>
|
||||
// Per-memory bookkeeping
|
||||
val portNS = Namespace(mem.readers ++ mem.writers)
|
||||
val rMap = mem.readwriters.map(rw => (rw -> portNS.newName(s"${rw}_r"))).toMap
|
||||
val wMap = mem.readwriters.map(rw => (rw -> portNS.newName(s"${rw}_w"))).toMap
|
||||
val newReaders = mem.readers ++ mem.readwriters.map(rMap(_))
|
||||
val newWriters = mem.writers ++ mem.readwriters.map(wMap(_))
|
||||
val newMem = DefMemory(mem.info, mem.name, mem.dataType, mem.depth, 1, 0, newReaders, newWriters, Nil)
|
||||
val rCmdDelay = if (mem.readUnderWrite == "old") 0 else mem.readLatency
|
||||
val rRespDelay = if (mem.readUnderWrite == "old") mem.readLatency else 0
|
||||
val wCmdDelay = mem.writeLatency - 1
|
||||
|
||||
val readStmts = (mem.readers ++ mem.readwriters).map { case r =>
|
||||
def oldDriver(f: String) = netlist(we(memPortField(mem, r, f)))
|
||||
def newField(f: String) = memPortField(newMem, rMap.getOrElse(r, r), f)
|
||||
val clk = oldDriver("clk")
|
||||
|
||||
// Pack sources of read command inputs into WithValid object -> different for readwriter
|
||||
val enSrc = if (rMap.contains(r)) AND(oldDriver("en"), NOT(oldDriver("wmode"))) else oldDriver("en")
|
||||
val cmdSrc = WithValid(enSrc, Seq(oldDriver("addr")))
|
||||
val cmdSink = WithValid(newField("en"), Seq(newField("addr")))
|
||||
val (cmdPiped, cmdDecls, cmdConns) = pipelineWithValid(ns)(clk, rCmdDelay, cmdSrc, nameTemplate = Some(cmdSink))
|
||||
val cmdPortConns = connect(cmdSink, cmdPiped) :+ connect(newField("clk"), clk)
|
||||
|
||||
// Pipeline read response using *last* command pipe stage enable as the valid signal
|
||||
val resp = WithValid(cmdPiped.valid, Seq(newField("data")))
|
||||
val respPipeNameTemplate = Some(resp.copy(valid = cmdSink.valid)) // base pipeline register names off field names
|
||||
val (respPiped, respDecls, respConns) = pipelineWithValid(ns)(clk, rRespDelay, resp, nameTemplate = respPipeNameTemplate)
|
||||
|
||||
// Make sure references to the read data get appropriately substituted
|
||||
val oldRDataName = if (rMap.contains(r)) "rdata" else "data"
|
||||
exprReplacements(we(memPortField(mem, r, oldRDataName))) = respPiped.payload.head
|
||||
|
||||
// Return all statements; they're separated so connects can go after all declarations
|
||||
SplitStatements(cmdDecls ++ respDecls, cmdConns ++ cmdPortConns ++ respConns)
|
||||
}
|
||||
|
||||
val writeStmts = (mem.writers ++ mem.readwriters).map { case w =>
|
||||
def oldDriver(f: String) = netlist(we(memPortField(mem, w, f)))
|
||||
def newField(f: String) = memPortField(newMem, wMap.getOrElse(w, w), f)
|
||||
val clk = oldDriver("clk")
|
||||
|
||||
// Pack sources of write command inputs into WithValid object -> different for readwriter
|
||||
val cmdSrc = if (wMap.contains(w)) {
|
||||
val en = AND(oldDriver("en"), oldDriver("wmode"))
|
||||
WithValid(en, Seq(oldDriver("addr"), oldDriver("wmask"), oldDriver("wdata")))
|
||||
} else {
|
||||
WithValid(oldDriver("en"), Seq(oldDriver("addr"), oldDriver("mask"), oldDriver("data")))
|
||||
}
|
||||
|
||||
// Pipeline write command, connect to memory
|
||||
val cmdSink = WithValid(newField("en"), Seq(newField("addr"), newField("mask"), newField("data")))
|
||||
val (cmdPiped, cmdDecls, cmdConns) = pipelineWithValid(ns)(clk, wCmdDelay, cmdSrc, nameTemplate = Some(cmdSink))
|
||||
val cmdPortConns = connect(cmdSink, cmdPiped) :+ connect(newField("clk"), clk)
|
||||
|
||||
// Return all statements; they're separated so connects can go after all declarations
|
||||
SplitStatements(cmdDecls, cmdConns ++ cmdPortConns)
|
||||
}
|
||||
|
||||
newConns ++= (readStmts ++ writeStmts).flatMap(_.conns)
|
||||
Block(newMem +: (readStmts ++ writeStmts).flatMap(_.decls))
|
||||
case sx: Connect if kind(sx.loc) == MemKind => EmptyStmt // Filter old mem connections
|
||||
case sx => sx.map(swapMemRefs)
|
||||
}
|
||||
|
||||
val transformed = m match {
|
||||
case mod: Module =>
|
||||
findMemConns(mod.body)
|
||||
mod.copy(body = Block(transform(mod.body) +: newConns.toSeq))
|
||||
case mod => mod
|
||||
}
|
||||
}
|
||||
|
||||
object VerilogMemDelays extends passes.Pass {
|
||||
def transform(m: DefModule): DefModule = (new MemDelayAndReadwriteTransformer(m)).transformed
|
||||
def run(c: Circuit): Circuit = c.copy(modules = c.modules.map(transform))
|
||||
}
|
|
@ -7,41 +7,55 @@ import ir._
|
|||
import annotations._
|
||||
|
||||
// Wrap the top module of a circuit with another module
|
||||
class WrapTop extends Transform {
|
||||
object WrapTop extends Transform {
|
||||
def inputForm = HighForm
|
||||
def outputForm = HighForm
|
||||
|
||||
// TODO: Make these names flexible
|
||||
// Previously, it looked like they were flexible in the code, but they weren't
|
||||
// This refactors them to reflect that fact
|
||||
val topWrapperName = "FAMETop"
|
||||
val hostClockName = "hostClock"
|
||||
val hostResetName = "hostReset"
|
||||
|
||||
def checkNames(c: Circuit, top: DefModule) = {
|
||||
val ns = Namespace(top.ports.map(_.name))
|
||||
val portsOK = ns.tryName(hostClockName) && ns.tryName(hostResetName)
|
||||
portsOK && ns.tryName(top.name) && Namespace(c).tryName(topWrapperName)
|
||||
}
|
||||
|
||||
override def execute(state: CircuitState): CircuitState = {
|
||||
val topName = state.circuit.main
|
||||
val topModule = state.circuit.modules.find(_.name == topName).get
|
||||
val circuitNS = Namespace(state.circuit)
|
||||
val topWrapperName = circuitNS.newName("FAMETop")
|
||||
val topWrapperNS = Namespace(topModule.ports.map(_.name))
|
||||
val topInstance = WDefInstance(topWrapperNS.newName(topName), topName)
|
||||
assert(checkNames(state.circuit, topModule))
|
||||
|
||||
val topInstance = WDefInstance(topName, topName)
|
||||
val portConnections = topModule.ports.map({
|
||||
case ip @ Port(_, name, Input, _) => Connect(NoInfo, WSubField(WRef(topInstance), name), WRef(ip))
|
||||
case op @ Port(_, name, Output, _) => Connect(NoInfo, WRef(op), WSubField(WRef(topInstance), name))
|
||||
})
|
||||
val clocks = topModule.ports.filter(_.tpe == ClockType)
|
||||
val hostClock = clocks.find(_.name == "clock").getOrElse(clocks.head)
|
||||
val hostReset = HostReset.makePort(topWrapperNS)
|
||||
|
||||
val hostClock = Port(NoInfo, hostClockName, Input, ClockType)
|
||||
val hostReset = Port(NoInfo, hostResetName, Input, Utils.BoolType)
|
||||
|
||||
val oldCircuitTarget = CircuitTarget(topName)
|
||||
val topWrapperTarget = ModuleTarget(topWrapperName, topWrapperName)
|
||||
val topWrapper = Module(NoInfo, topWrapperName, topModule.ports :+ hostReset, Block(topInstance +: portConnections))
|
||||
val topWrapper = Module(NoInfo, topWrapperName, hostClock +: hostReset +: topModule.ports, Block(topInstance +: portConnections))
|
||||
val specialPortAnnotations = Seq(FAMEHostClock(topWrapperTarget.ref(hostClock.name)), FAMEHostReset(topWrapperTarget.ref(hostReset.name)))
|
||||
|
||||
val renames = RenameMap()
|
||||
val newCircuit = Circuit(state.circuit.info, topWrapper +: state.circuit.modules, topWrapperName)
|
||||
// Make channel annotations point at top-level ports
|
||||
val fccaRenames = RenameMap()
|
||||
fccaRenames.record(oldCircuitTarget.module(topName), oldCircuitTarget.module(topWrapperName))
|
||||
|
||||
val updatedAnnotations = state.annotations.map({
|
||||
case fca: FAMEChannelConnectionAnnotation =>
|
||||
fca.copy(sinks = fca.sinks.map(_.map(_.copy(module = topWrapperName))), sources = fca.sources.map(_.map(_.copy(module = topWrapperName))))
|
||||
case a => a
|
||||
}).map({ // Also update targets in info fields
|
||||
case fca @ FAMEChannelConnectionAnnotation(_,info@DecoupledForwardChannel(_,_,_,_),_,_) =>
|
||||
fca.copy(channelInfo = info.copy(
|
||||
readySink = info.readySink. map(_.copy(module = topWrapperName)),
|
||||
validSource = info.validSource.map(_.copy(module = topWrapperName)),
|
||||
readySource = info.readySource.map(_.copy(module = topWrapperName)),
|
||||
validSink = info.validSink. map(_.copy(module = topWrapperName))))
|
||||
case fcca: FAMEChannelConnectionAnnotation =>
|
||||
val renamedInfo = fcca.channelInfo match {
|
||||
case fwd: DecoupledForwardChannel => fwd.update(fccaRenames)
|
||||
case info => info
|
||||
}
|
||||
fcca.copy(channelInfo = renamedInfo).update(fccaRenames).head // always returns 1
|
||||
case a => a
|
||||
})
|
||||
|
||||
|
|
|
@ -15,22 +15,12 @@ import scala.language.implicitConversions
|
|||
|
||||
|
||||
package object passes {
|
||||
|
||||
/**
|
||||
* A utility for keeping statements defining and connecting signals to a piece of hardware
|
||||
* together with a reference to the component. This is useful for passes that insert hardware,
|
||||
* since the "collateral" of that object can be kept in one place.
|
||||
*/
|
||||
trait WrappedComponent {
|
||||
val decl: Statement
|
||||
val assigns: Statement
|
||||
val ref: Expression
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the definition of a signal along with the statements that assign to it and its reference.
|
||||
*/
|
||||
case class SignalInfo(decl: Statement, assigns: Statement, ref: Expression) extends WrappedComponent
|
||||
case class SignalInfo(decl: Statement, assigns: Statement, ref: Expression)
|
||||
|
||||
/**
|
||||
* A utility for creating a wire that "echoes" the value of an existing expression.
|
||||
|
@ -44,29 +34,6 @@ package object passes {
|
|||
}
|
||||
}
|
||||
|
||||
object InstanceInfo {
|
||||
def apply(m: DefModule)(implicit ns: Namespace): InstanceInfo = {
|
||||
val inst = fame.Instantiate(m, ns.newName(m.name))
|
||||
InstanceInfo(inst, Block(Nil), WRef(inst))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the declaration of an instance, along with the set of statements that create connections
|
||||
* to its ports, along with a reference to the instance.
|
||||
*/
|
||||
case class InstanceInfo(decl: WDefInstance, assigns: Block, ref: WRef) extends WrappedComponent {
|
||||
def addAssign(s: Statement): InstanceInfo = {
|
||||
copy(assigns = Block(assigns.stmts :+ s))
|
||||
}
|
||||
def connect(pName: String, rhs: Expression): InstanceInfo = {
|
||||
addAssign(Connect(NoInfo, WSubField(ref, pName), rhs))
|
||||
}
|
||||
def connect(lhs: Expression, pName: String): InstanceInfo = {
|
||||
addAssign(Connect(NoInfo, lhs, WSubField(ref, pName)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This pass ensures that the AbstractClockGate blackbox is defined in a circuit, so that it can
|
||||
* later be instantiated. The blackbox clock gate has the following signature:
|
||||
|
|
|
@ -15,7 +15,8 @@ class AssertBundle(val numAsserts: Int) extends Bundle {
|
|||
val asserts = Output(UInt(numAsserts.W))
|
||||
}
|
||||
|
||||
class AssertBridgeModule(numAsserts: Int)(implicit p: Parameters) extends BridgeModule[HostPortIO[UInt]]()(p) {
|
||||
class AssertBridgeModule(assertMessages: Seq[String])(implicit p: Parameters) extends BridgeModule[HostPortIO[UInt]]()(p) {
|
||||
val numAsserts = assertMessages.size
|
||||
val io = IO(new WidgetIO())
|
||||
val hPort = IO(HostPort(Input(UInt(numAsserts.W))))
|
||||
val resume = WireInit(false.B)
|
||||
|
@ -43,4 +44,12 @@ class AssertBridgeModule(numAsserts: Int)(implicit p: Parameters) extends Bridge
|
|||
genROReg(cycles >> 32, "cycle_high")
|
||||
Pulsify(genWORegInit(resume, "resume", false.B), pulseLength = 1)
|
||||
genCRFile()
|
||||
|
||||
override def genHeader(base: BigInt, sb: StringBuilder) {
|
||||
import CppGenerationUtils._
|
||||
val headerWidgetName = getWName.toUpperCase
|
||||
super.genHeader(base, sb)
|
||||
sb.append(genConstStatic(s"${headerWidgetName}_assert_count", UInt32(assertMessages.size)))
|
||||
sb.append(genArray(s"${headerWidgetName}_assert_messages", assertMessages.map(CStrLit)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package widgets
|
|||
|
||||
import chisel3._
|
||||
import chisel3.util._
|
||||
import chisel3.experimental.{DataMirror, Direction}
|
||||
import chisel3.util.experimental.BoringUtils
|
||||
import freechips.rocketchip.config.{Parameters, Field}
|
||||
import freechips.rocketchip.diplomacy.AddressSet
|
||||
|
@ -13,28 +12,29 @@ trait AutoCounterConsts {
|
|||
val counterWidth = 64
|
||||
}
|
||||
|
||||
class AutoCounterBundle(val numCounters: Int) extends Bundle with AutoCounterConsts {
|
||||
val counters = Input(Vec(numCounters, UInt(counterWidth.W)))
|
||||
class AutoCounterBundle(eventNames: Seq[String], triggerName: String) extends Record {
|
||||
val triggerEnable = Input(Bool())
|
||||
val events = eventNames.map(_ -> Input(Bool()))
|
||||
val elements = collection.immutable.ListMap(((triggerName, triggerEnable) +:
|
||||
events):_*)
|
||||
override def cloneType = new AutoCounterBundle(eventNames, triggerName).asInstanceOf[this.type]
|
||||
}
|
||||
|
||||
case class AutoCounterBridgeConstArgs(numcounters: Int, autoCounterPortsMap: scala.collection.mutable.Map[Int, String], hastracerwidget: Boolean = false)
|
||||
|
||||
class AutoCounterToHostToken(val numCounters: Int) extends Bundle {
|
||||
val data_out = Vec(numCounters, UInt(64.W))
|
||||
val cycle = UInt(64.W)
|
||||
class AutoCounterToHostToken(val numCounters: Int) extends Bundle with AutoCounterConsts {
|
||||
val data_out = Vec(numCounters, UInt(counterWidth.W))
|
||||
val cycle = UInt(counterWidth.W)
|
||||
}
|
||||
|
||||
class AutoCounterBridgeModule(constructorArg: AutoCounterBridgeConstArgs)(implicit p: Parameters) extends BridgeModule[HostPortIO[AutoCounterBundle]]()(p) {
|
||||
|
||||
val numCounters = constructorArg.numcounters
|
||||
val labels = constructorArg.autoCounterPortsMap
|
||||
val hastracerwidget = constructorArg.hastracerwidget
|
||||
val trigger = WireDefault(true.B)
|
||||
class AutoCounterBridgeModule(events: Seq[(String, String)], triggerName: String)(implicit p: Parameters)
|
||||
extends BridgeModule[HostPortIO[AutoCounterBundle]]()(p) with AutoCounterConsts {
|
||||
val numCounters = events.size
|
||||
val (portNames, labels) = events.unzip
|
||||
|
||||
val io = IO(new WidgetIO())
|
||||
val hPort = IO(HostPort(new AutoCounterBundle(numCounters)))
|
||||
val cycles = RegInit(0.U(64.W))
|
||||
val acc_cycles = RegInit(0.U(64.W))
|
||||
val hPort = IO(HostPort(new AutoCounterBundle(portNames, triggerName)))
|
||||
val trigger = hPort.hBits.triggerEnable
|
||||
val cycles = RegInit(0.U(counterWidth.W))
|
||||
val acc_cycles = RegInit(0.U(counterWidth.W))
|
||||
|
||||
val hostCyclesWidthOffset = 64 - p(CtrlNastiKey).dataBits
|
||||
val hostCyclesLowWidth = if (hostCyclesWidthOffset > 0) p(CtrlNastiKey).dataBits else 64
|
||||
|
@ -50,83 +50,64 @@ class AutoCounterBridgeModule(constructorArg: AutoCounterBridgeConstArgs)(implic
|
|||
|
||||
val readrate_low = RegInit(0.U(hostReadrateLowWidth.W))
|
||||
val readrate_high = RegInit(0.U(hostReadrateHighWidth.W))
|
||||
val readrate = Wire(UInt(64.W))
|
||||
val readrate_dly = RegInit(0.U(64.W))
|
||||
readrate := Cat(readrate_high, readrate_low)
|
||||
val readrate = Cat(readrate_high, readrate_low)
|
||||
val initDone = RegInit(false.B)
|
||||
|
||||
val acc_counters = RegInit(VecInit(Seq.fill(numCounters)(0.U(64.W))))
|
||||
|
||||
hastracerwidget match {
|
||||
case true => BoringUtils.addSink(trigger, s"trace_trigger")
|
||||
case _ => trigger := true.B
|
||||
}
|
||||
|
||||
val tFireHelper = DecoupledHelper(hPort.toHost.hValid, hPort.fromHost.hReady)
|
||||
val tFireHelper = DecoupledHelper(hPort.toHost.hValid, hPort.fromHost.hReady, initDone)
|
||||
val targetFire = tFireHelper.fire
|
||||
// We only sink tokens, so tie off the return channel
|
||||
hPort.fromHost.hValid := true.B
|
||||
|
||||
when (targetFire) {
|
||||
cycles := cycles + 1.U
|
||||
readrate_dly := readrate
|
||||
}
|
||||
|
||||
val periodcycles = RegInit(1.U(64.W))
|
||||
when (targetFire & (readrate =/= readrate_dly)) {
|
||||
periodcycles := readrate - 2.U
|
||||
} .elsewhen (targetFire & (periodcycles === 0.U) & (readrate > 0.U)) {
|
||||
periodcycles := readrate - 1.U
|
||||
} .elsewhen (targetFire & (cycles > 0.U) & (readrate > 0.U)) {
|
||||
periodcycles := periodcycles - 1.U
|
||||
val counters = hPort.hBits.events.unzip._2.map({ increment =>
|
||||
val count = RegInit(0.U(counterWidth.W))
|
||||
when (targetFire && increment) {
|
||||
count := count + 1.U
|
||||
}
|
||||
count
|
||||
}).toSeq
|
||||
|
||||
val periodcycles = RegInit(0.U(64.W))
|
||||
val isSampleCycle = periodcycles === readrate
|
||||
when (targetFire && isSampleCycle) {
|
||||
periodcycles := 0.U
|
||||
} .elsewhen (targetFire) {
|
||||
periodcycles := periodcycles + 1.U
|
||||
}
|
||||
|
||||
val btht_queue = Module(new Queue(new AutoCounterToHostToken(numCounters), 10))
|
||||
val btht_queue = Module(new Queue(new AutoCounterToHostToken(numCounters), 2))
|
||||
|
||||
btht_queue.io.enq.valid := (periodcycles === 0.U) & (cycles > 0.U) & (cycles >= readrate-1.U) & targetFire & trigger
|
||||
btht_queue.io.enq.bits.data_out := hPort.hBits.counters
|
||||
btht_queue.io.enq.valid := isSampleCycle & targetFire & trigger
|
||||
btht_queue.io.enq.bits.data_out := VecInit(counters)
|
||||
btht_queue.io.enq.bits.cycle := cycles
|
||||
hPort.toHost.hReady := btht_queue.io.enq.ready & hPort.fromHost.hReady
|
||||
hPort.toHost.hReady := targetFire
|
||||
|
||||
val (lowCountAddrs, highCountAddrs) = (for ((counter, label) <- btht_queue.io.deq.bits.data_out.zip(labels)) yield {
|
||||
val lowAddr = attach(counter(hostCounterLowWidth-1, 0), s"autocounter_low_${label}", ReadOnly)
|
||||
val highAddr = attach(counter >> hostCounterLowWidth, s"autocounter_high_${label}", ReadOnly)
|
||||
(lowAddr, highAddr)
|
||||
}).unzip
|
||||
|
||||
//communication with the driver
|
||||
val readdone = RegInit(false.B)
|
||||
val readdone_dly = RegInit(false.B)
|
||||
val readdone_negedge = Wire(Bool())
|
||||
val readdone_posedge = Wire(Bool())
|
||||
val med = RegInit(false.B)
|
||||
readdone_dly := readdone
|
||||
readdone_posedge := readdone & ~readdone_dly
|
||||
readdone_negedge := readdone_dly & ~readdone
|
||||
|
||||
|
||||
when (btht_queue.io.deq.fire()) {
|
||||
for (i <- 0 to numCounters-1) { acc_counters(i) := btht_queue.io.deq.bits.data_out(i) }
|
||||
acc_cycles := btht_queue.io.deq.bits.cycle
|
||||
med := false.B
|
||||
} .elsewhen (readdone_posedge) {
|
||||
med := true.B
|
||||
} .elsewhen (readdone_negedge) {
|
||||
med := false.B
|
||||
}
|
||||
|
||||
btht_queue.io.deq.ready := med
|
||||
|
||||
|
||||
labels.keys.foreach {
|
||||
case (index) => {
|
||||
attach(acc_counters(index)(hostCounterLowWidth-1, 0), s"autocounter_low_${labels(index)}", ReadOnly)
|
||||
attach(acc_counters(index) >> hostCounterLowWidth, s"autocounter_high_${labels(index)}", ReadOnly)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
attach(acc_cycles(hostCyclesLowWidth-1, 0), "cycles_low", ReadOnly)
|
||||
attach(acc_cycles >> hostCyclesLowWidth, "cycles_high", ReadOnly)
|
||||
attach(btht_queue.io.deq.bits.cycle(hostCyclesLowWidth-1, 0), "cycles_low", ReadOnly)
|
||||
attach(btht_queue.io.deq.bits.cycle >> hostCyclesLowWidth, "cycles_high", ReadOnly)
|
||||
attach(readrate_low, "readrate_low", WriteOnly)
|
||||
attach(readrate_high, "readrate_high", WriteOnly)
|
||||
attach(initDone, "init_done", WriteOnly)
|
||||
attach(btht_queue.io.deq.valid, "countersready", ReadOnly)
|
||||
attach(readdone, "readdone", WriteOnly)
|
||||
|
||||
Pulsify(genWORegInit(btht_queue.io.deq.ready, "readdone", false.B), 1)
|
||||
|
||||
override def genHeader(base: BigInt, sb: StringBuilder) {
|
||||
headerComment(sb)
|
||||
// Exclude counter addresses as their names can vary across AutoCounter instances, but
|
||||
// we only generate a single struct typedef
|
||||
val headerWidgetName = wName.getOrElse(name).toUpperCase
|
||||
crRegistry.genHeader(headerWidgetName, base, sb, lowCountAddrs ++ highCountAddrs)
|
||||
crRegistry.genArrayHeader(headerWidgetName, base, sb)
|
||||
emitClockDomainInfo(headerWidgetName, sb)
|
||||
}
|
||||
genCRFile()
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import midas.core.{SimWrapperChannels, SimUtils}
|
|||
import midas.core.SimUtils.{RVChTuple}
|
||||
import midas.passes.fame.{FAMEChannelConnectionAnnotation,DecoupledForwardChannel, PipeChannel, DecoupledReverseChannel, WireChannel, JsonProtocol, HasSerializationHints}
|
||||
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.config.{Parameters, Field}
|
||||
|
||||
import chisel3._
|
||||
import chisel3.util._
|
||||
|
@ -23,11 +23,23 @@ import scala.reflect.runtime.{universe => ru}
|
|||
*
|
||||
*/
|
||||
|
||||
// Set in FPGA Top before the BridgeModule is generated
|
||||
case object TargetClockInfo extends Field[Option[RationalClock]]
|
||||
|
||||
abstract class TokenizedRecord extends Record with HasChannels
|
||||
|
||||
abstract class BridgeModule[HostPortType <: TokenizedRecord]
|
||||
(implicit p: Parameters) extends Widget()(p) {
|
||||
def hPort: HostPortType
|
||||
def clockDomainInfo: RationalClock = p(TargetClockInfo).get
|
||||
def emitClockDomainInfo(headerWidgetName: String, sb: StringBuilder): Unit = {
|
||||
import CppGenerationUtils._
|
||||
val RationalClock(domainName, mul, div) = clockDomainInfo
|
||||
sb.append(genStatic(s"${headerWidgetName}_clock_domain_name", CStrLit(domainName)))
|
||||
sb.append(genConstStatic(s"${headerWidgetName}_clock_multiplier", UInt32(mul)))
|
||||
sb.append(genConstStatic(s"${headerWidgetName}_clock_divisor", UInt32(div)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trait Bridge[HPType <: TokenizedRecord, WidgetType <: BridgeModule[HPType]] {
|
||||
|
@ -85,9 +97,17 @@ trait HasChannels {
|
|||
* Implementation follows
|
||||
*
|
||||
*/
|
||||
private[midas] def getClock(): Clock = {
|
||||
val allTargetClocks = SimUtils.findClocks(targetPortProto)
|
||||
require(allTargetClocks.nonEmpty,
|
||||
s"Target-side bridge interface of ${targetPortProto.getClass} has no clock field.")
|
||||
require(allTargetClocks.size == 1,
|
||||
s"Target-side bridge interface of ${targetPortProto.getClass} has ${allTargetClocks.size} clocks but must define only one.")
|
||||
allTargetClocks.head
|
||||
}
|
||||
|
||||
private[midas] def inputChannelNames(): Seq[String] = inputWireChannels.map(_._2)
|
||||
private[midas] def outputChannelNames(): Seq[String] = outputWireChannels.map(_._2)
|
||||
private[midas] def inputChannelNames(): Seq[String] = inputWireChannels.map(_._2)
|
||||
|
||||
private def getRVChannelNames(channels: Seq[RVChTuple]): Seq[String] =
|
||||
channels.flatMap({ channel =>
|
||||
|
@ -113,9 +133,9 @@ trait HasChannels {
|
|||
for ((field, chName) <- channels) {
|
||||
annotate(new ChiselAnnotation { def toFirrtl =
|
||||
if (bridgeSunk) {
|
||||
FAMEChannelConnectionAnnotation.source(chName, PipeChannel(latency), Seq(field.toNamed.toTarget))
|
||||
FAMEChannelConnectionAnnotation.source(chName, PipeChannel(latency), Some(getClock.toNamed.toTarget), Seq(field.toNamed.toTarget))
|
||||
} else {
|
||||
FAMEChannelConnectionAnnotation.sink (chName, PipeChannel(latency), Seq(field.toNamed.toTarget))
|
||||
FAMEChannelConnectionAnnotation.sink(chName, PipeChannel(latency), Some(getClock.toNamed.toTarget), Seq(field.toNamed.toTarget))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -127,6 +147,7 @@ trait HasChannels {
|
|||
// Generate the forward channel annotation
|
||||
val (fwdChName, revChName) = SimUtils.rvChannelNamePair(chName)
|
||||
annotate(new ChiselAnnotation { def toFirrtl = {
|
||||
val clockTarget = Some(getClock.toNamed.toTarget)
|
||||
val validTarget = field.valid.toNamed.toTarget
|
||||
val readyTarget = field.ready.toNamed.toTarget
|
||||
val leafTargets = Seq(validTarget) ++ lowerAggregateIntoLeafTargets(field.bits)
|
||||
|
@ -135,6 +156,7 @@ trait HasChannels {
|
|||
FAMEChannelConnectionAnnotation.source(
|
||||
fwdChName,
|
||||
DecoupledForwardChannel.source(validTarget, readyTarget),
|
||||
clockTarget,
|
||||
leafTargets
|
||||
)
|
||||
} else {
|
||||
|
@ -142,17 +164,19 @@ trait HasChannels {
|
|||
FAMEChannelConnectionAnnotation.sink(
|
||||
fwdChName,
|
||||
DecoupledForwardChannel.sink(validTarget, readyTarget),
|
||||
clockTarget,
|
||||
leafTargets
|
||||
)
|
||||
}
|
||||
}})
|
||||
|
||||
annotate(new ChiselAnnotation { def toFirrtl = {
|
||||
val clockTarget = Some(getClock.toNamed.toTarget)
|
||||
val readyTarget = Seq(field.ready.toNamed.toTarget)
|
||||
if (bridgeSunk) {
|
||||
FAMEChannelConnectionAnnotation.sink(revChName, DecoupledReverseChannel, readyTarget)
|
||||
FAMEChannelConnectionAnnotation.sink(revChName, DecoupledReverseChannel, clockTarget, readyTarget)
|
||||
} else {
|
||||
FAMEChannelConnectionAnnotation.source(revChName, DecoupledReverseChannel, readyTarget)
|
||||
FAMEChannelConnectionAnnotation.source(revChName, DecoupledReverseChannel, clockTarget, readyTarget)
|
||||
}
|
||||
}})
|
||||
}
|
||||
|
|
|
@ -99,6 +99,9 @@ case class InMemoryBridgeAnnotation(
|
|||
* @param channelMapping A mapping from the channel names initially emitted by the Chisel Module, to uniquified global ones
|
||||
* to find associated FCCAs for this bridge
|
||||
*
|
||||
* @param clockInfo Contains information about the domain in which the bridge is instantiated.
|
||||
* This will always be nonEmpty for bridges instantiated in the input FIRRTL
|
||||
*
|
||||
* @param widget An optional lambda to elaborate the host-land BridgeModule. See InMemoryBridgeAnnotation
|
||||
*
|
||||
* @param widgetClass The BridgeModule's full class name. See SerializableBridgeAnnotation
|
||||
|
@ -110,6 +113,7 @@ case class InMemoryBridgeAnnotation(
|
|||
private[midas] case class BridgeIOAnnotation(
|
||||
val target: ReferenceTarget,
|
||||
channelMapping: Map[String, String],
|
||||
clockInfo: Option[RationalClock] = None,
|
||||
widget: Option[(Parameters) => BridgeModule[_ <: TokenizedRecord]] = None,
|
||||
widgetClass: Option[String] = None,
|
||||
widgetConstructorKey: Option[_ <: AnyRef] = None) extends SingleTargetAnnotation[ReferenceTarget] {
|
||||
|
@ -119,17 +123,20 @@ private[midas] case class BridgeIOAnnotation(
|
|||
// Elaborates the BridgeModule using the lambda if it exists
|
||||
// Otherwise, uses reflection to find the constructor for the class given by
|
||||
// widgetClass, passing it the widgetConstructorKey
|
||||
def elaborateWidget(implicit p: Parameters): BridgeModule[_ <: TokenizedRecord] = widget match {
|
||||
case Some(elaborator) => elaborator(p)
|
||||
case None =>
|
||||
println(s"Instantiating bridge ${target.ref} of type ${widgetClass.get}")
|
||||
val constructor = Class.forName(widgetClass.get).getConstructors()(0)
|
||||
(widgetConstructorKey match {
|
||||
case Some(key) =>
|
||||
println(s" With constructor arguments: $key")
|
||||
constructor.newInstance(key, p)
|
||||
case None => constructor.newInstance(p)
|
||||
}).asInstanceOf[BridgeModule[_ <: TokenizedRecord]]
|
||||
def elaborateWidget(implicit p: Parameters): BridgeModule[_ <: TokenizedRecord] = {
|
||||
val px = p alterPartial { case TargetClockInfo => clockInfo }
|
||||
widget match {
|
||||
case Some(elaborator) => elaborator(px)
|
||||
case None =>
|
||||
println(s"Instantiating bridge ${target.ref} of type ${widgetClass.get}")
|
||||
val constructor = Class.forName(widgetClass.get).getConstructors()(0)
|
||||
(widgetConstructorKey match {
|
||||
case Some(key) =>
|
||||
println(s" With constructor arguments: $key")
|
||||
constructor.newInstance(key, px)
|
||||
case None => constructor.newInstance(px)
|
||||
}).asInstanceOf[BridgeModule[_ <: TokenizedRecord]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +146,6 @@ private[midas] object BridgeIOAnnotation {
|
|||
def apply(target: ReferenceTarget,
|
||||
widget: (Parameters) => BridgeModule[_ <: TokenizedRecord],
|
||||
channelNames: Seq[String]): BridgeIOAnnotation =
|
||||
BridgeIOAnnotation(target, channelNames.map(p => p -> p).toMap, Some(widget))
|
||||
BridgeIOAnnotation(target, channelNames.map(p => p -> p).toMap, widget = Some(widget))
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ abstract class ChannelizedHostPortIO(protected val targetPortProto: Data) extend
|
|||
lazy val fieldToChannelMap = Map((_inputWireChannels ++ _outputWireChannels):_*)
|
||||
|
||||
private def getLeafDirs(token: Data): Seq[Direction] = token match {
|
||||
case c: Clock => Seq()
|
||||
case c: Clock => throw new Exception("Tokens cannot contain clock fields")
|
||||
case b: Record => b.elements.flatMap({ case (_, e) => getLeafDirs(e)}).toSeq
|
||||
case v: Vec[_] => v.flatMap(getLeafDirs)
|
||||
case b: Bits => Seq(directionOf(b))
|
||||
|
|
|
@ -0,0 +1,207 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.widgets
|
||||
|
||||
import midas.core.{SimWrapperChannels, SimUtils}
|
||||
import midas.core.SimUtils.{RVChTuple}
|
||||
import midas.passes.fame.{FAMEChannelConnectionAnnotation, TargetClockChannel}
|
||||
|
||||
import freechips.rocketchip.config.Parameters
|
||||
import freechips.rocketchip.util.DensePrefixSum
|
||||
|
||||
import chisel3._
|
||||
import chisel3.util._
|
||||
import chisel3.experimental.{BaseModule, Direction, ChiselAnnotation, annotate}
|
||||
import firrtl.annotations.{ModuleTarget, ReferenceTarget}
|
||||
|
||||
/**
|
||||
* Defines a generated clock as a rational multiple of some reference clock. The generated
|
||||
* clock has a frequency (multiplier / divisor) times that of reference.
|
||||
*
|
||||
* @param name An identifier for the associated clock domain
|
||||
*
|
||||
* @param multiplier See class comment.
|
||||
*
|
||||
* @param divisor See class comment.
|
||||
*/
|
||||
case class RationalClock(name: String, multiplier: Int, divisor: Int)
|
||||
|
||||
sealed trait ClockBridgeConsts {
|
||||
val clockChannelName = "clocks"
|
||||
val refClockDomain = "baseClock"
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom bridge annotation for the Clock Bridge. Unique so that we can
|
||||
* trivially match against it in bridge extraction.
|
||||
*
|
||||
* @param target The target-side module for the CB
|
||||
*
|
||||
* @param clocks The associated clock information for each output clock (including the base).
|
||||
*/
|
||||
|
||||
case class ClockBridgeAnnotation(val target: ModuleTarget, clocks: Seq[RationalClock])
|
||||
extends BridgeAnnotation with ClockBridgeConsts {
|
||||
val channelNames = Seq(clockChannelName)
|
||||
def duplicate(n: ModuleTarget) = this.copy(target)
|
||||
def toIOAnnotation(port: String): BridgeIOAnnotation = {
|
||||
val channelMapping = channelNames.map(oldName => oldName -> s"${port}_$oldName")
|
||||
BridgeIOAnnotation(
|
||||
target.copy(module = target.circuit).ref(port),
|
||||
channelMapping.toMap,
|
||||
widget = Some((p: Parameters) => new ClockBridgeModule(clocks)(p))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The default target-side clock bridge. Generates a "base clock" and a vector of
|
||||
* additional clocks related to that base clock. Simulation times are
|
||||
* generally expressed in terms of this base clock.
|
||||
*
|
||||
* @param additionalClocks Rational clock information for each additional
|
||||
* clock beyond the base
|
||||
*/
|
||||
class RationalClockBridge(additionalClocks: RationalClock*) extends BlackBox with ClockBridgeConsts {
|
||||
outer =>
|
||||
// Always generate the base (element 0 in our output vec)
|
||||
val baseClock = RationalClock(refClockDomain, 1, 1)
|
||||
val allClocks = baseClock +: additionalClocks
|
||||
val io = IO(new Bundle {
|
||||
val clocks = Output(Vec(allClocks.size, Clock()))
|
||||
})
|
||||
|
||||
// Generate the bridge annotation
|
||||
annotate(new ChiselAnnotation { def toFirrtl = ClockBridgeAnnotation( outer.toTarget, allClocks) })
|
||||
annotate(new ChiselAnnotation { def toFirrtl =
|
||||
FAMEChannelConnectionAnnotation(
|
||||
clockChannelName,
|
||||
channelInfo = TargetClockChannel(allClocks),
|
||||
clock = None, // Clock channels do not have a reference clock
|
||||
sinks = Some(io.clocks.map(_.toTarget)),
|
||||
sources = None
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* The host-land clock bridge interface. This consists of a single channel,
|
||||
* carrying clock tokens. A clock token is a Vec[Bool], one element per clock, When a bit is set,
|
||||
* that clock domain will fire in the simulator time step that consumes this clock token.
|
||||
*
|
||||
* NB: The target-time elapsed between tokens is not necessarily constant.
|
||||
*
|
||||
* @param numClocks The total number of clocks in the channel (inclusive of the base clock)
|
||||
*
|
||||
*/
|
||||
class ClockTokenVector(numClocks: Int) extends TokenizedRecord with ClockBridgeConsts {
|
||||
def targetPortProto(): Vec[Bool] = Vec(numClocks, Bool())
|
||||
val clocks = new DecoupledIO(targetPortProto)
|
||||
|
||||
def outputWireChannels = Seq(clocks -> clockChannelName)
|
||||
def inputWireChannels = Seq()
|
||||
def outputRVChannels = Seq()
|
||||
def inputRVChannels = Seq()
|
||||
|
||||
def connectChannels2Port(bridgeAnno: BridgeIOAnnotation, simIo: SimWrapperChannels): Unit = {
|
||||
val local2globalName = bridgeAnno.channelMapping.toMap
|
||||
for (localName <- outputChannelNames) {
|
||||
simIo.clockElement._2 <> elements(localName)
|
||||
}
|
||||
}
|
||||
|
||||
val elements = collection.immutable.ListMap(clockChannelName -> clocks)
|
||||
override def cloneType(): this.type = new ClockTokenVector(numClocks).asInstanceOf[this.type]
|
||||
def generateAnnotations(): Unit = {}
|
||||
}
|
||||
|
||||
/**
|
||||
* The host-side implementation. Based on provided a clock information, generates a clock token stream
|
||||
* which will be sunk by the FAME-1 hub model. This token stream does not
|
||||
* depend on the runtime-behavior of the target, allowing this bridge run
|
||||
* ahead of the rest of the simulation.
|
||||
*
|
||||
* Target and host time measurements provided by simif_t are facilitated with MMIO to this bridge
|
||||
*
|
||||
* @param clockInfo Clock frequency information for each target clock
|
||||
*
|
||||
*/
|
||||
class ClockBridgeModule(clockInfo: Seq[RationalClock])(implicit p: Parameters)
|
||||
extends BridgeModule[ClockTokenVector] {
|
||||
val io = IO(new WidgetIO())
|
||||
val hPort = IO(new ClockTokenVector(clockInfo.size))
|
||||
val phaseRelationships = clockInfo map { cInfo => (cInfo.multiplier, cInfo.divisor) }
|
||||
val clockTokenGen = Module(new RationalClockTokenGenerator(phaseRelationships))
|
||||
hPort.clocks <> clockTokenGen.io
|
||||
|
||||
val hCycleName = "hCycle"
|
||||
val hCycle = genWideRORegInit(0.U(64.W), hCycleName)
|
||||
hCycle := hCycle + 1.U
|
||||
|
||||
// Count the number of clock tokens for which the fastest clock is scheduled to fire
|
||||
// --> Use to calculate FMR
|
||||
val tCycleFastest = genWideRORegInit(0.U(64.W), "tCycle")
|
||||
val fastestClockIdx = (phaseRelationships).map({ case (n, d) => n.toDouble / d })
|
||||
.zipWithIndex
|
||||
.sortBy(_._1)
|
||||
.last._2
|
||||
|
||||
when (hPort.clocks.fire && hPort.clocks.bits(fastestClockIdx)) {
|
||||
tCycleFastest := tCycleFastest + 1.U
|
||||
}
|
||||
genCRFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a virtual fast-clock whose period is the GCD of the periods of all requested
|
||||
* clocks, and returns the period of each requested clock as an integer multiple of that
|
||||
* high-frequency virtual clock.
|
||||
*/
|
||||
object FindScaledPeriodGCD {
|
||||
def apply(phaseRelationships: Seq[(Int, Int)]): Seq[BigInt] = {
|
||||
val periodDivisors = phaseRelationships.unzip._1
|
||||
val productOfDivisors = periodDivisors.foldLeft(BigInt(1))(_ * _)
|
||||
val scaledMultipliers = phaseRelationships.map({ case (divisor, multiplier) => multiplier * productOfDivisors / divisor })
|
||||
val gcdOfScaledPeriods = scaledMultipliers.reduce((a, b) => a.gcd(b))
|
||||
val reducedPeriods = scaledMultipliers.map(_ / gcdOfScaledPeriods)
|
||||
reducedPeriods
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an infinite clock token stream based on rational relationship of each clock.
|
||||
* To improve simulator FMR, this module always produces non-zero clock tokens
|
||||
*
|
||||
* @param phaseRelationships multiplier, divisor pairs for each clock
|
||||
*/
|
||||
class RationalClockTokenGenerator(phaseRelationships: Seq[(Int, Int)]) extends Module {
|
||||
val numClocks = phaseRelationships.size
|
||||
val io = IO(new DecoupledIO(Vec(numClocks, Bool())))
|
||||
// The clock token stream is known a priori!
|
||||
io.valid := true.B
|
||||
|
||||
// Determine the number of virtual-clock cycles for each target clock.
|
||||
val clockPeriodicity = FindScaledPeriodGCD(phaseRelationships)
|
||||
val counterWidth = clockPeriodicity.map(p => log2Ceil(p + 1)).reduce((a, b) => math.max(a, b))
|
||||
|
||||
// This is an arbitrarily selected number; feel free to increase it. If we
|
||||
// need more time resolution we can trivially pipeline this thing.
|
||||
val maxCounterWidth = 16
|
||||
require(counterWidth <= maxCounterWidth, "Ensure this circuit doesn't blow up")
|
||||
|
||||
// For each target clock, count the number of virtual cycles until the next expected clock edge
|
||||
val timeToNextEdge = RegInit(VecInit(Seq.fill(numClocks)(0.U(counterWidth.W))))
|
||||
// Find the smallest number of virtual-clock cycles that must must advance
|
||||
// before one real clock would fire.
|
||||
val minStepsToEdge = DensePrefixSum(timeToNextEdge)({ case (a, b) => Mux(a < b, a, b) }).last
|
||||
|
||||
// Advance the virtual clock (minStepsToEdge) cycles, and determine which
|
||||
// target clocks have an edge at that time to populate the clock token
|
||||
io.bits := VecInit(for ((reg, period) <- timeToNextEdge.zip(clockPeriodicity)) yield {
|
||||
val clockFiring = reg === minStepsToEdge
|
||||
when (io.ready) {
|
||||
reg := Mux(clockFiring, period.U, reg - minStepsToEdge)
|
||||
}
|
||||
clockFiring
|
||||
})
|
||||
}
|
|
@ -3,12 +3,12 @@
|
|||
package midas
|
||||
package widgets
|
||||
|
||||
trait CPPLiteral {
|
||||
sealed trait CPPLiteral {
|
||||
def typeString: String
|
||||
def toC: String
|
||||
}
|
||||
|
||||
trait IntLikeLiteral extends CPPLiteral {
|
||||
sealed trait IntLikeLiteral extends CPPLiteral {
|
||||
def bitWidth: Int
|
||||
def literalSuffix: String
|
||||
def value: BigInt
|
||||
|
@ -31,7 +31,7 @@ case class UInt64(value: BigInt) extends IntLikeLiteral {
|
|||
|
||||
case class CStrLit(val value: String) extends CPPLiteral {
|
||||
def typeString = "const char* const"
|
||||
def toC = "\"%s\"".format(value)
|
||||
def toC = "R\"ESC(%s)ESC\"".format(value)
|
||||
}
|
||||
|
||||
object CppGenerationUtils {
|
||||
|
@ -64,6 +64,7 @@ object CppGenerationUtils {
|
|||
|
||||
def genComment(str: String): String = "// %s\n".format(str)
|
||||
|
||||
implicit def toStrLit(str: String): CStrLit = CStrLit(str)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -23,7 +23,6 @@ import scala.collection.mutable
|
|||
* (It is also possible to use this for very simple models where the one or
|
||||
* more outputs depend combinationally on a _single_ input token (the toHost
|
||||
* field))
|
||||
*
|
||||
*/
|
||||
|
||||
// We're using a Record here because reflection in Bundle prematurely initializes our lazy vals
|
||||
|
@ -64,7 +63,7 @@ class HostPortIO[+T <: Data](protected val targetPortProto: T) extends Tokenized
|
|||
field := tokenChannel.bits
|
||||
toHostChannels += tokenChannel
|
||||
}
|
||||
|
||||
|
||||
for ((field, localName) <- outputWireChannels) {
|
||||
val tokenChannel = simIo.wireInputPortMap(local2globalName(localName))
|
||||
tokenChannel.bits := field
|
||||
|
@ -105,6 +104,9 @@ class HostPortIO[+T <: Data](protected val targetPortProto: T) extends Tokenized
|
|||
// Enqueue into the toHost channels only once all toHost channels can accept the token
|
||||
val fromHostHelper = DecoupledHelper((fromHost.hValid +: fromHostChannels.map(_.ready)):_*)
|
||||
fromHostChannels.foreach(ch => ch.valid := fromHostHelper.fire(ch.ready))
|
||||
|
||||
// Tie off the target clock; these should be unused in the BridgeModule
|
||||
SimUtils.findClocks(hBits).map(_ := false.B.asClock)
|
||||
}
|
||||
|
||||
def generateAnnotations(): Unit = {
|
||||
|
|
|
@ -225,16 +225,18 @@ class MCRFileMap() {
|
|||
case (e: RegisterEntry, addr) => mcrIO.bindReg(e, addr)
|
||||
}
|
||||
|
||||
def genHeader(prefix: String, base: BigInt, sb: StringBuilder): Unit = {
|
||||
def genHeader(prefix: String, base: BigInt, sb: StringBuilder, addrsToExclude: Seq[Int] = Nil): Unit = {
|
||||
// get widget name with no widget number (prefix includes it)
|
||||
val prefix_no_num = prefix.split("_")(0)
|
||||
|
||||
val filteredRegs = name2addr.toList.filterNot({ case (_, idx) => addrsToExclude.contains(idx) })
|
||||
|
||||
// emit generic struct for this widget type. guarded so it only gets
|
||||
// defined once
|
||||
sb append s"#ifndef ${prefix_no_num}_struct_guard\n"
|
||||
sb append s"#define ${prefix_no_num}_struct_guard\n"
|
||||
sb append s"typedef struct ${prefix_no_num}_struct {\n"
|
||||
name2addr.toList foreach { case (regName, idx) =>
|
||||
filteredRegs foreach { case (regName, idx) =>
|
||||
sb append s" unsigned long ${regName};\n"
|
||||
}
|
||||
sb append s"} ${prefix_no_num}_struct;\n"
|
||||
|
@ -248,7 +250,7 @@ class MCRFileMap() {
|
|||
sb append s"#define ${prefix}_substruct_create \\\n"
|
||||
// assume the widget destructor will free this
|
||||
sb append s"${prefix_no_num}_struct * ${prefix}_substruct = (${prefix_no_num}_struct *) malloc(sizeof(${prefix_no_num}_struct)); \\\n"
|
||||
name2addr.toList foreach { case (regName, idx) =>
|
||||
filteredRegs foreach { case (regName, idx) =>
|
||||
val address = base + idx
|
||||
sb append s"${prefix}_substruct->${regName} = ${address}; \\\n"
|
||||
}
|
||||
|
|
|
@ -51,9 +51,6 @@ class PeekPokeBridgeModule(key: PeekPokeKey)(implicit p: Parameters) extends Bri
|
|||
val tCycle = genWideRORegInit(0.U(64.W), tCycleName)
|
||||
val tCycleAdvancing = WireInit(false.B)
|
||||
|
||||
val hCycleName = "hCycle"
|
||||
val hCycle = genWideRORegInit(0.U(64.W), hCycleName)
|
||||
|
||||
// needs back pressure from reset queues
|
||||
io.idle := cycleHorizon === 0.U
|
||||
|
||||
|
@ -137,8 +134,6 @@ class PeekPokeBridgeModule(key: PeekPokeKey)(implicit p: Parameters) extends Bri
|
|||
tCycleAdvancing := true.B
|
||||
}
|
||||
|
||||
hCycle := hCycle + 1.U
|
||||
|
||||
when (io.step.fire) {
|
||||
cycleHorizon := io.step.bits
|
||||
}
|
||||
|
@ -201,8 +196,10 @@ object PeekPokeTokenizedIO {
|
|||
}
|
||||
|
||||
class PeekPokeTargetIO(targetIO: Seq[(String, Data)], withReset: Boolean) extends Record {
|
||||
val clock = Input(Clock())
|
||||
val reset = if (withReset) Some(Output(Bool())) else None
|
||||
override val elements = ListMap((
|
||||
Seq("clock" -> clock) ++
|
||||
reset.map("reset" -> _).toSeq ++
|
||||
targetIO.map({ case (name, field) => name -> Flipped(chiselTypeOf(field)) })
|
||||
):_*)
|
||||
|
@ -219,10 +216,11 @@ class PeekPokeBridge(targetIO: Seq[(String, Data)], reset: Option[Bool]) extends
|
|||
|
||||
object PeekPokeBridge {
|
||||
@chiselName
|
||||
def apply(reset: Bool, ioList: (String, Data)*): PeekPokeBridge = {
|
||||
def apply(clock: Clock, reset: Bool, ioList: (String, Data)*): PeekPokeBridge = {
|
||||
val peekPokeBridge = Module(new PeekPokeBridge(ioList, Some(reset)))
|
||||
ioList.foreach({ case (name, field) => field <> peekPokeBridge.io.elements(name) })
|
||||
reset := peekPokeBridge.io.reset.get
|
||||
peekPokeBridge.io.clock := clock
|
||||
peekPokeBridge
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,13 @@ class PrintRecord(portType: firrtl.ir.BundleType, val formatString: String) exte
|
|||
def regenLeafType(tpe: firrtl.ir.Type): Data = tpe match {
|
||||
case firrtl.ir.UIntType(width: firrtl.ir.IntWidth) => UInt(width.width.toInt.W)
|
||||
case firrtl.ir.SIntType(width: firrtl.ir.IntWidth) => SInt(width.width.toInt.W)
|
||||
case firrtl.ir.SIntType(width: firrtl.ir.IntWidth) => SInt(width.width.toInt.W)
|
||||
case badType => throw new RuntimeException(s"Unexpected type in PrintBundle: ${badType}")
|
||||
}
|
||||
|
||||
val args: Seq[(String, Data)] = portType.fields.collect({
|
||||
case firrtl.ir.Field(name, _, tpe) if name != "enable" => (name -> Output(regenLeafType(tpe)))
|
||||
case firrtl.ir.Field(name, _, tpe) if name != "enable" && name != "clock" =>
|
||||
(name -> Output(regenLeafType(tpe)))
|
||||
})
|
||||
|
||||
val enable = Output(Bool())
|
||||
|
@ -184,6 +186,7 @@ class PrintBridgeModule(printPorts: Seq[(firrtl.ir.Port, String)])(implicit p: P
|
|||
sb.append(genArray(s"${headerWidgetName}_format_strings", formatStrings))
|
||||
sb.append(genArray(s"${headerWidgetName}_argument_counts", argumentCounts))
|
||||
sb.append(genArray(s"${headerWidgetName}_argument_widths", argumentWidths))
|
||||
emitClockDomainInfo(headerWidgetName, sb)
|
||||
}
|
||||
genCRFile()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package goldengate.tests
|
||||
|
||||
import midas.passes._
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.annotations._
|
||||
// Switch to FIRRTL in 3.2
|
||||
import midas.firrtl.testutils._
|
||||
|
||||
class BridgeTopWiringSpec extends MiddleTransformSpec with FirrtlRunners {
|
||||
|
||||
def transform = new BridgeTopWiring("t_")
|
||||
|
||||
"The signal x in module A" should s"should be wired to Top with the correct clocks" in {
|
||||
val input =
|
||||
"""circuit Top :
|
||||
| module Top :
|
||||
| input clock1 : Clock
|
||||
| input clock2 : Clock
|
||||
| inst a1 of A
|
||||
| a1.clock <= clock1
|
||||
| inst a2 of A
|
||||
| a2.clock <= clock2
|
||||
| module A :
|
||||
| input clock : Clock
|
||||
| wire x : UInt<1>
|
||||
| x <= UInt(1)
|
||||
""".stripMargin
|
||||
val aIT = ModuleTarget("Top", "A")
|
||||
val topMT = ModuleTarget("Top", "Top")
|
||||
val annos = Seq(BridgeTopWiringAnnotation(aIT.ref("x"), aIT.ref("clock")))
|
||||
val checkAnnos = Seq(
|
||||
BridgeTopWiringOutputAnnotation(aIT.ref("x"),
|
||||
aIT.addHierarchy("Top", "a1").ref("x"),
|
||||
topMT.ref("t_a1_x"),
|
||||
topMT.ref("t_clock1")),
|
||||
BridgeTopWiringOutputAnnotation(aIT.ref("x"),
|
||||
aIT.addHierarchy("Top", "a2").ref("x"),
|
||||
topMT.ref("t_a2_x"),
|
||||
topMT.ref("t_clock2")))
|
||||
val check =
|
||||
"""circuit Top :
|
||||
| module Top :
|
||||
| input clock1: Clock
|
||||
| input clock2: Clock
|
||||
| output t_a1_x: UInt<1>
|
||||
| output t_a2_x: UInt<1>
|
||||
| output t_clock1: Clock
|
||||
| output t_clock2: Clock
|
||||
| inst a1 of A
|
||||
| inst a2 of A
|
||||
| a1.clock <= clock1
|
||||
| a2.clock <= clock2
|
||||
| t_a1_x <= a1.t_x
|
||||
| t_a2_x <= a2.t_x
|
||||
| t_clock1 <= clock1
|
||||
| t_clock2 <= clock2
|
||||
| module A :
|
||||
| input clock : Clock
|
||||
| output t_x : UInt<1>
|
||||
| wire x : UInt<1>
|
||||
| x <= UInt(1)
|
||||
| t_x <= x
|
||||
""".stripMargin
|
||||
executeWithAnnos(input, check, annos, checkAnnos)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
|
||||
package goldengate.tests
|
||||
|
||||
import midas.passes._
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.annotations._
|
||||
// Switch to FIRRTL in 3.2
|
||||
import midas.firrtl.testutils._
|
||||
|
||||
|
||||
class FindClockSourceSpec extends LowTransformSpec {
|
||||
def transform = FindClockSources
|
||||
|
||||
def executeAnnosOnly(input: String, annotations: Seq[Annotation], checkAnnotations: Seq[Annotation]): CircuitState = {
|
||||
val finalState = compileAndEmit(CircuitState(parse(input), ChirrtlForm, annotations))
|
||||
|
||||
annotations.foreach { anno =>
|
||||
logger.debug(anno.serialize)
|
||||
}
|
||||
|
||||
finalState.annotations.toSeq.foreach { anno =>
|
||||
logger.debug(anno.serialize)
|
||||
}
|
||||
|
||||
val csAnnos = finalState.annotations.collect { case a: ClockSourceAnnotation => a }
|
||||
checkAnnotations.foreach { check => csAnnos should contain (check) }
|
||||
finalState
|
||||
}
|
||||
|
||||
"FindClockSources" should s"find sources for submodule clocks" in {
|
||||
val input =
|
||||
"""circuit Top :
|
||||
| module Top :
|
||||
| input clock : Clock
|
||||
| input clock2 : Clock
|
||||
| inst a1 of A
|
||||
| a1.clock <= clock
|
||||
| module A :
|
||||
| input clock : Clock
|
||||
""".stripMargin
|
||||
val aClockRT = ModuleTarget("Top", "A").addHierarchy("Top", "a1").ref("clock")
|
||||
val topClockRT = ModuleTarget("Top", "Top").ref("clock")
|
||||
val annos = Seq(FindClockSourceAnnotation(aClockRT))
|
||||
val checkAnnos = Seq(ClockSourceAnnotation(aClockRT, Some(topClockRT)))
|
||||
executeAnnosOnly(input, annos, checkAnnos)
|
||||
}
|
||||
|
||||
it should s"find sources that pass through other modules" in {
|
||||
val input =
|
||||
"""circuit Top :
|
||||
| module Top :
|
||||
| input clock : Clock
|
||||
| input clock2 : Clock
|
||||
| inst a1 of A
|
||||
| inst p of PassThru
|
||||
| p.iclock <= clock
|
||||
| a1.clock <= p.oclock
|
||||
| module A :
|
||||
| input clock : Clock
|
||||
| module PassThru :
|
||||
| input iclock : Clock
|
||||
| output oclock : Clock
|
||||
| oclock <= iclock
|
||||
""".stripMargin
|
||||
val aClockRT = ModuleTarget("Top", "A").addHierarchy("Top", "a1").ref("clock")
|
||||
val topClockRT = ModuleTarget("Top", "Top").ref("clock")
|
||||
val annos = Seq(FindClockSourceAnnotation(aClockRT))
|
||||
val checkAnnos = Seq(ClockSourceAnnotation(aClockRT, Some(topClockRT)))
|
||||
executeAnnosOnly(input, annos, checkAnnos)
|
||||
}
|
||||
|
||||
it should s"find sources for clocks at the root of the module hiearchy" in {
|
||||
val input =
|
||||
"""circuit Top :
|
||||
| module Top :
|
||||
| input clock : Clock
|
||||
| input clock2 : Clock
|
||||
| wire c : Clock
|
||||
| c <= clock2
|
||||
""".stripMargin
|
||||
val topClockRT = ModuleTarget("Top", "Top").ref("clock2")
|
||||
val wireClockRT = ModuleTarget("Top", "Top").ref("c")
|
||||
val annos = Seq(FindClockSourceAnnotation(wireClockRT))
|
||||
val checkAnnos = Seq(ClockSourceAnnotation(wireClockRT, Some(topClockRT)))
|
||||
executeAnnosOnly(input, annos, checkAnnos)
|
||||
}
|
||||
|
||||
it should s"find sources for intermediate nodes in a chain of clock connections" in {
|
||||
val input =
|
||||
"""circuit Top :
|
||||
| module Top :
|
||||
| input clock : Clock
|
||||
| input clock2 : Clock
|
||||
| wire c : Clock
|
||||
| wire d : Clock
|
||||
| c <= clock2
|
||||
| d <= c
|
||||
""".stripMargin
|
||||
val topClockRT = ModuleTarget("Top", "Top").ref("clock2")
|
||||
val cClockRT = ModuleTarget("Top", "Top").ref("c")
|
||||
val dClockRT = ModuleTarget("Top", "Top").ref("d")
|
||||
val annos = Seq(FindClockSourceAnnotation(cClockRT),
|
||||
FindClockSourceAnnotation(dClockRT))
|
||||
val checkAnnos = Seq(ClockSourceAnnotation(cClockRT, Some(topClockRT)),
|
||||
ClockSourceAnnotation(dClockRT, Some(topClockRT)))
|
||||
executeAnnosOnly(input, annos, checkAnnos)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.firrtl.testutils
|
||||
|
||||
import java.io._
|
||||
import java.security.Permission
|
||||
|
||||
import com.typesafe.scalalogging.LazyLogging
|
||||
|
||||
import scala.sys.process._
|
||||
import org.scalatest._
|
||||
import org.scalatest.prop._
|
||||
|
||||
import firrtl._
|
||||
import firrtl.ir._
|
||||
import firrtl.Parser.{IgnoreInfo, UseInfo}
|
||||
import firrtl.analyses.{GetNamespace, InstanceGraph, ModuleNamespaceAnnotation}
|
||||
import firrtl.annotations._
|
||||
import firrtl.transforms.{DontTouchAnnotation, NoDedupAnnotation, RenameModules}
|
||||
import firrtl.util.BackendCompilationUtilities
|
||||
import scala.collection.mutable
|
||||
|
||||
class CheckLowForm extends SeqTransform {
|
||||
def inputForm = LowForm
|
||||
def outputForm = LowForm
|
||||
def transforms = Seq(
|
||||
passes.CheckHighForm
|
||||
)
|
||||
}
|
||||
|
||||
trait FirrtlRunners extends BackendCompilationUtilities {
|
||||
|
||||
val cppHarnessResourceName: String = "/firrtl/testTop.cpp"
|
||||
/** Extra transforms to run by default */
|
||||
val extraCheckTransforms = Seq(new CheckLowForm)
|
||||
|
||||
private class RenameTop(newTopPrefix: String) extends Transform {
|
||||
def inputForm: LowForm.type = LowForm
|
||||
def outputForm: LowForm.type = LowForm
|
||||
|
||||
def execute(state: CircuitState): CircuitState = {
|
||||
val namespace = state.annotations.collectFirst {
|
||||
case m: ModuleNamespaceAnnotation => m
|
||||
}.get.namespace
|
||||
|
||||
val newTopName = namespace.newName(newTopPrefix)
|
||||
val modulesx = state.circuit.modules.map {
|
||||
case mod: Module if mod.name == state.circuit.main => mod.mapString(_ => newTopName)
|
||||
case other => other
|
||||
}
|
||||
|
||||
state.copy(circuit = state.circuit.copy(main = newTopName, modules = modulesx))
|
||||
}
|
||||
}
|
||||
|
||||
/** Check equivalence of Firrtl transforms using yosys
|
||||
*
|
||||
* @param input string containing Firrtl source
|
||||
* @param customTransforms Firrtl transforms to test for equivalence
|
||||
* @param customAnnotations Optional Firrtl annotations
|
||||
* @param resets tell yosys which signals to set for SAT, format is (timestep, signal, value)
|
||||
*/
|
||||
def firrtlEquivalenceTest(input: String,
|
||||
customTransforms: Seq[Transform] = Seq.empty,
|
||||
customAnnotations: AnnotationSeq = Seq.empty,
|
||||
resets: Seq[(Int, String, Int)] = Seq.empty): Unit = {
|
||||
val circuit = Parser.parse(input.split("\n").toIterator)
|
||||
val compiler = new MinimumVerilogCompiler
|
||||
val prefix = circuit.main
|
||||
val testDir = createTestDirectory(prefix + "_equivalence_test")
|
||||
val firrtlWriter = new PrintWriter(s"${testDir.getAbsolutePath}/$prefix.fir")
|
||||
firrtlWriter.write(input)
|
||||
firrtlWriter.close()
|
||||
|
||||
val customVerilog = compiler.compileAndEmit(CircuitState(circuit, HighForm, customAnnotations),
|
||||
new GetNamespace +: new RenameTop(s"${prefix}_custom") +: customTransforms)
|
||||
val namespaceAnnotation = customVerilog.annotations.collectFirst { case m: ModuleNamespaceAnnotation => m }.get
|
||||
val customTop = customVerilog.circuit.main
|
||||
val customFile = new PrintWriter(s"${testDir.getAbsolutePath}/$customTop.v")
|
||||
customFile.write(customVerilog.getEmittedCircuit.value)
|
||||
customFile.close()
|
||||
|
||||
val referenceVerilog = compiler.compileAndEmit(CircuitState(circuit, HighForm, Seq(namespaceAnnotation)),
|
||||
Seq(new RenameModules, new RenameTop(s"${prefix}_reference")))
|
||||
val referenceTop = referenceVerilog.circuit.main
|
||||
val referenceFile = new PrintWriter(s"${testDir.getAbsolutePath}/$referenceTop.v")
|
||||
referenceFile.write(referenceVerilog.getEmittedCircuit.value)
|
||||
referenceFile.close()
|
||||
|
||||
assert(yosysExpectSuccess(customTop, referenceTop, testDir, resets))
|
||||
}
|
||||
|
||||
/** Compiles input Firrtl to Verilog */
|
||||
def compileToVerilog(input: String, annotations: AnnotationSeq = Seq.empty): String = {
|
||||
val circuit = Parser.parse(input.split("\n").toIterator)
|
||||
val compiler = new VerilogCompiler
|
||||
val res = compiler.compileAndEmit(CircuitState(circuit, HighForm, annotations), extraCheckTransforms)
|
||||
res.getEmittedCircuit.value
|
||||
}
|
||||
/** Compile a Firrtl file
|
||||
*
|
||||
* @param prefix is the name of the Firrtl file without path or file extension
|
||||
* @param srcDir directory where all Resources for this test are located
|
||||
* @param annotations Optional Firrtl annotations
|
||||
*/
|
||||
def compileFirrtlTest(
|
||||
prefix: String,
|
||||
srcDir: String,
|
||||
customTransforms: Seq[Transform] = Seq.empty,
|
||||
annotations: AnnotationSeq = Seq.empty): File = {
|
||||
val testDir = createTestDirectory(prefix)
|
||||
copyResourceToFile(s"${srcDir}/${prefix}.fir", new File(testDir, s"${prefix}.fir"))
|
||||
|
||||
val optionsManager = new ExecutionOptionsManager(prefix) with HasFirrtlOptions {
|
||||
commonOptions = CommonOptions(topName = prefix, targetDirName = testDir.getPath)
|
||||
firrtlOptions = FirrtlExecutionOptions(
|
||||
infoModeName = "ignore",
|
||||
customTransforms = customTransforms ++ extraCheckTransforms,
|
||||
annotations = annotations.toList)
|
||||
}
|
||||
firrtl.Driver.execute(optionsManager)
|
||||
|
||||
testDir
|
||||
}
|
||||
/** Execute a Firrtl Test
|
||||
*
|
||||
* @param prefix is the name of the Firrtl file without path or file extension
|
||||
* @param srcDir directory where all Resources for this test are located
|
||||
* @param verilogPrefixes names of option Verilog resources without path or file extension
|
||||
* @param annotations Optional Firrtl annotations
|
||||
*/
|
||||
def runFirrtlTest(
|
||||
prefix: String,
|
||||
srcDir: String,
|
||||
verilogPrefixes: Seq[String] = Seq.empty,
|
||||
customTransforms: Seq[Transform] = Seq.empty,
|
||||
annotations: AnnotationSeq = Seq.empty) = {
|
||||
val testDir = compileFirrtlTest(prefix, srcDir, customTransforms, annotations)
|
||||
val harness = new File(testDir, s"top.cpp")
|
||||
copyResourceToFile(cppHarnessResourceName, harness)
|
||||
|
||||
// Note file copying side effect
|
||||
val verilogFiles = verilogPrefixes map { vprefix =>
|
||||
val file = new File(testDir, s"$vprefix.v")
|
||||
copyResourceToFile(s"$srcDir/$vprefix.v", file)
|
||||
file
|
||||
}
|
||||
|
||||
verilogToCpp(prefix, testDir, verilogFiles, harness).!
|
||||
cppToExe(prefix, testDir).!
|
||||
assert(executeExpectingSuccess(prefix, testDir))
|
||||
}
|
||||
}
|
||||
|
||||
trait FirrtlMatchers extends Matchers {
|
||||
def dontTouch(path: String): Annotation = {
|
||||
val parts = path.split('.')
|
||||
require(parts.size >= 2, "Must specify both module and component!")
|
||||
val name = ComponentName(parts.tail.mkString("."), ModuleName(parts.head, CircuitName("Top")))
|
||||
DontTouchAnnotation(name)
|
||||
}
|
||||
def dontDedup(mod: String): Annotation = {
|
||||
require(mod.split('.').size == 1, "Can only specify a Module, not a component or instance")
|
||||
NoDedupAnnotation(ModuleName(mod, CircuitName("Top")))
|
||||
}
|
||||
// Replace all whitespace with a single space and remove leading and
|
||||
// trailing whitespace
|
||||
// Note this is intended for single-line strings, no newlines
|
||||
def normalized(s: String): String = {
|
||||
require(!s.contains("\n"))
|
||||
s.replaceAll("\\s+", " ").trim
|
||||
}
|
||||
/** Helper to make circuits that are the same appear the same */
|
||||
def canonicalize(circuit: Circuit): Circuit = {
|
||||
import firrtl.Mappers._
|
||||
def onModule(mod: DefModule) = mod.map(firrtl.Utils.squashEmpty)
|
||||
circuit.map(onModule)
|
||||
}
|
||||
def parse(str: String) = Parser.parse(str.split("\n").toIterator, UseInfo)
|
||||
/** Helper for executing tests
|
||||
* compiler will be run on input then emitted result will each be split into
|
||||
* lines and normalized.
|
||||
*/
|
||||
def executeTest(
|
||||
input: String,
|
||||
expected: Seq[String],
|
||||
compiler: Compiler,
|
||||
annotations: Seq[Annotation] = Seq.empty) = {
|
||||
val finalState = compiler.compileAndEmit(CircuitState(parse(input), ChirrtlForm, annotations))
|
||||
val lines = finalState.getEmittedCircuit.value split "\n" map normalized
|
||||
for (e <- expected) {
|
||||
lines should contain (e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object FirrtlCheckers extends FirrtlMatchers {
|
||||
import matchers._
|
||||
implicit class TestingFunctionsOnCircuitState(val state: CircuitState) extends AnyVal {
|
||||
def search(pf: PartialFunction[Any, Boolean]): Boolean = state.circuit.search(pf)
|
||||
}
|
||||
implicit class TestingFunctionsOnCircuit(val circuit: Circuit) extends AnyVal {
|
||||
def search(pf: PartialFunction[Any, Boolean]): Boolean = {
|
||||
val f = pf.lift
|
||||
def rec(node: Any): Boolean = {
|
||||
f(node) match {
|
||||
// If the partial function is defined on this node, return its result
|
||||
case Some(res) => res
|
||||
// Otherwise keep digging
|
||||
case None =>
|
||||
require(node.isInstanceOf[Product] || !node.isInstanceOf[FirrtlNode],
|
||||
"Error! Unexpected FirrtlNode that does not implement Product!")
|
||||
val iter = node match {
|
||||
case p: Product => p.productIterator
|
||||
case i: Iterable[Any] => i.iterator
|
||||
case _ => Iterator.empty
|
||||
}
|
||||
iter.foldLeft(false) {
|
||||
case (res, elt) => if (res) res else rec(elt)
|
||||
}
|
||||
}
|
||||
}
|
||||
rec(circuit)
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks that the emitted circuit has the expected line, both will be normalized */
|
||||
def containLine(expectedLine: String) = containLines(expectedLine)
|
||||
|
||||
/** Checks that the emitted circuit has the expected lines in order, all lines will be normalized */
|
||||
def containLines(expectedLines: String*) = new CircuitStateStringsMatcher(expectedLines)
|
||||
|
||||
class CircuitStateStringsMatcher(expectedLines: Seq[String]) extends Matcher[CircuitState] {
|
||||
override def apply(state: CircuitState): MatchResult = {
|
||||
val emitted = state.getEmittedCircuit.value
|
||||
MatchResult(
|
||||
emitted.split("\n").map(normalized).containsSlice(expectedLines.map(normalized)),
|
||||
emitted + "\n did not contain \"" + expectedLines + "\"",
|
||||
s"${state.circuit.main} contained $expectedLines"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
def containTree(pf: PartialFunction[Any, Boolean]) = new CircuitStatePFMatcher(pf)
|
||||
|
||||
class CircuitStatePFMatcher(pf: PartialFunction[Any, Boolean]) extends Matcher[CircuitState] {
|
||||
override def apply(state: CircuitState): MatchResult = {
|
||||
MatchResult(
|
||||
state.search(pf),
|
||||
state.circuit.serialize + s"\n did not contain $pf",
|
||||
s"${state.circuit.main} contained $pf"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class FirrtlPropSpec extends PropSpec with PropertyChecks with FirrtlRunners with LazyLogging
|
||||
|
||||
abstract class FirrtlFlatSpec extends FlatSpec with FirrtlRunners with FirrtlMatchers with LazyLogging
|
||||
|
||||
// Who tests the testers?
|
||||
class TestFirrtlFlatSpec extends FirrtlFlatSpec {
|
||||
import FirrtlCheckers._
|
||||
|
||||
val c = parse("""
|
||||
|circuit Test:
|
||||
| module Test :
|
||||
| input in : UInt<8>
|
||||
| output out : UInt<8>
|
||||
| out <= in
|
||||
|""".stripMargin)
|
||||
val state = CircuitState(c, ChirrtlForm)
|
||||
val compiled = (new LowFirrtlCompiler).compileAndEmit(state, List.empty)
|
||||
|
||||
// While useful, ScalaTest helpers should be used over search
|
||||
behavior of "Search"
|
||||
|
||||
it should "be supported on Circuit" in {
|
||||
assert(c search {
|
||||
case Connect(_, Reference("out",_), Reference("in",_)) => true
|
||||
})
|
||||
}
|
||||
it should "be supported on CircuitStates" in {
|
||||
assert(state search {
|
||||
case Connect(_, Reference("out",_), Reference("in",_)) => true
|
||||
})
|
||||
}
|
||||
it should "be supported on the results of compilers" in {
|
||||
assert(compiled search {
|
||||
case Connect(_, WRef("out",_,_,_), WRef("in",_,_,_)) => true
|
||||
})
|
||||
}
|
||||
|
||||
// Use these!!!
|
||||
behavior of "ScalaTest helpers"
|
||||
|
||||
they should "work for lines of emitted text" in {
|
||||
compiled should containLine (s"input in : UInt<8>")
|
||||
compiled should containLine (s"output out : UInt<8>")
|
||||
compiled should containLine (s"out <= in")
|
||||
}
|
||||
|
||||
they should "work for partial functions matching on subtrees" in {
|
||||
val UInt8 = UIntType(IntWidth(8)) // BigInt unapply is weird
|
||||
compiled should containTree { case Port(_, "in", Input, UInt8) => true }
|
||||
compiled should containTree { case Port(_, "out", Output, UInt8) => true }
|
||||
compiled should containTree { case Connect(_, WRef("out",_,_,_), WRef("in",_,_,_)) => true }
|
||||
}
|
||||
}
|
||||
|
||||
/** Super class for execution driven Firrtl tests */
|
||||
abstract class ExecutionTest(name: String, dir: String, vFiles: Seq[String] = Seq.empty) extends FirrtlPropSpec {
|
||||
property(s"$name should execute correctly") {
|
||||
runFirrtlTest(name, dir, vFiles)
|
||||
}
|
||||
}
|
||||
/** Super class for compilation driven Firrtl tests */
|
||||
abstract class CompilationTest(name: String, dir: String) extends FirrtlPropSpec {
|
||||
property(s"$name should compile correctly") {
|
||||
compileFirrtlTest(name, dir)
|
||||
}
|
||||
}
|
||||
|
||||
trait Utils {
|
||||
|
||||
/** Run some Scala thunk and return STDOUT and STDERR as strings.
|
||||
* @param thunk some Scala code
|
||||
* @return a tuple containing STDOUT, STDERR, and what the thunk returns
|
||||
*/
|
||||
def grabStdOutErr[T](thunk: => T): (String, String, T) = {
|
||||
val stdout, stderr = new ByteArrayOutputStream()
|
||||
val ret = scala.Console.withOut(stdout) { scala.Console.withErr(stderr) { thunk } }
|
||||
(stdout.toString, stderr.toString, ret)
|
||||
}
|
||||
|
||||
/** Encodes a System.exit exit code
|
||||
* @param status the exit code
|
||||
*/
|
||||
private case class ExitException(status: Int) extends SecurityException(s"Found a sys.exit with code $status")
|
||||
|
||||
/** A security manager that converts calls to System.exit into [[ExitException]]s by explicitly disabling the ability of
|
||||
* a thread to actually exit. For more information, see:
|
||||
* - https://docs.oracle.com/javase/tutorial/essential/environment/security.html
|
||||
*/
|
||||
private class ExceptOnExit extends SecurityManager {
|
||||
override def checkPermission(perm: Permission): Unit = {}
|
||||
override def checkPermission(perm: Permission, context: Object): Unit = {}
|
||||
override def checkExit(status: Int): Unit = {
|
||||
super.checkExit(status)
|
||||
throw ExitException(status)
|
||||
}
|
||||
}
|
||||
|
||||
/** Encodes a file that some code tries to write to
|
||||
* @param the file name
|
||||
*/
|
||||
private case class WriteException(file: String) extends SecurityException(s"Tried to write to file $file")
|
||||
|
||||
/** A security manager that converts writes to any file into [[WriteException]]s.
|
||||
*/
|
||||
private class ExceptOnWrite extends SecurityManager {
|
||||
override def checkPermission(perm: Permission): Unit = {}
|
||||
override def checkPermission(perm: Permission, context: Object): Unit = {}
|
||||
override def checkWrite(file: String): Unit = {
|
||||
super.checkWrite(file)
|
||||
throw WriteException(file)
|
||||
}
|
||||
}
|
||||
|
||||
/** Run some Scala code (a thunk) in an environment where all System.exit are caught and returned. This avoids a
|
||||
* situation where a test results in something actually exiting and killing the entire test. This is necessary if you
|
||||
* want to test a command line program, e.g., the `main` method of [[firrtl.options.Stage Stage]].
|
||||
*
|
||||
* NOTE: THIS WILL NOT WORK IN SITUATIONS WHERE THE THUNK IS CATCHING ALL [[Exception]]s OR [[Throwable]]s, E.G.,
|
||||
* SCOPT. IF THIS IS HAPPENING THIS WILL NOT WORK. REPEAT THIS WILL NOT WORK.
|
||||
* @param thunk some Scala code
|
||||
* @return either the output of the thunk (`Right[T]`) or an exit code (`Left[Int]`)
|
||||
*/
|
||||
def catchStatus[T](thunk: => T): Either[Int, T] = {
|
||||
try {
|
||||
System.setSecurityManager(new ExceptOnExit())
|
||||
Right(thunk)
|
||||
} catch {
|
||||
case ExitException(a) => Left(a)
|
||||
} finally {
|
||||
System.setSecurityManager(null)
|
||||
}
|
||||
}
|
||||
|
||||
/** Run some Scala code (a thunk) in an environment where file writes are caught and the file that a program tries to
|
||||
* write to is returned. This is useful if you want to test that some thunk either tries to write to a specific file
|
||||
* or doesn't try to write at all.
|
||||
*/
|
||||
def catchWrites[T](thunk: => T): Either[String, T] = {
|
||||
try {
|
||||
System.setSecurityManager(new ExceptOnWrite())
|
||||
Right(thunk)
|
||||
} catch {
|
||||
case WriteException(a) => Left(a)
|
||||
} finally {
|
||||
System.setSecurityManager(null)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
// See LICENSE for license details.
|
||||
|
||||
package midas.firrtl.testutils
|
||||
|
||||
import java.io.{StringWriter,Writer}
|
||||
import org.scalatest.{FlatSpec, Matchers}
|
||||
import org.scalatest.junit.JUnitRunner
|
||||
import firrtl.ir.Circuit
|
||||
import firrtl.Parser.UseInfo
|
||||
import firrtl.passes.{Pass, PassExceptions, RemoveEmpty}
|
||||
import firrtl._
|
||||
import firrtl.annotations._
|
||||
import logger._
|
||||
|
||||
// An example methodology for testing Firrtl Passes
|
||||
// Spec class should extend this class
|
||||
abstract class SimpleTransformSpec extends FlatSpec with FirrtlMatchers with Compiler with LazyLogging {
|
||||
// Utility function
|
||||
def squash(c: Circuit): Circuit = RemoveEmpty.run(c)
|
||||
|
||||
// Executes the test. Call in tests.
|
||||
// annotations cannot have default value because scalatest trait Suite has a default value
|
||||
def execute(input: String, check: String, annotations: Seq[Annotation]): CircuitState = {
|
||||
val finalState = compileAndEmit(CircuitState(parse(input), ChirrtlForm, annotations))
|
||||
val actual = RemoveEmpty.run(parse(finalState.getEmittedCircuit.value)).serialize
|
||||
val expected = parse(check).serialize
|
||||
logger.debug(actual)
|
||||
logger.debug(expected)
|
||||
(actual) should be (expected)
|
||||
finalState
|
||||
}
|
||||
|
||||
def executeWithAnnos(input: String, check: String, annotations: Seq[Annotation],
|
||||
checkAnnotations: Seq[Annotation]): CircuitState = {
|
||||
val finalState = compileAndEmit(CircuitState(parse(input), ChirrtlForm, annotations))
|
||||
val actual = RemoveEmpty.run(parse(finalState.getEmittedCircuit.value)).serialize
|
||||
val expected = parse(check).serialize
|
||||
logger.debug(actual)
|
||||
logger.debug(expected)
|
||||
(actual) should be (expected)
|
||||
|
||||
annotations.foreach { anno =>
|
||||
logger.debug(anno.serialize)
|
||||
}
|
||||
|
||||
finalState.annotations.toSeq.foreach { anno =>
|
||||
logger.debug(anno.serialize)
|
||||
}
|
||||
checkAnnotations.foreach { check =>
|
||||
(finalState.annotations.toSeq) should contain (check)
|
||||
}
|
||||
finalState
|
||||
}
|
||||
// Executes the test, should throw an error
|
||||
// No default to be consistent with execute
|
||||
def failingexecute(input: String, annotations: Seq[Annotation]): Exception = {
|
||||
intercept[PassExceptions] {
|
||||
compile(CircuitState(parse(input), ChirrtlForm, annotations), Seq.empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CustomResolveAndCheck(form: CircuitForm) extends SeqTransform {
|
||||
def inputForm = form
|
||||
def outputForm = form
|
||||
def transforms: Seq[Transform] = Seq[Transform](new ResolveAndCheck)
|
||||
}
|
||||
|
||||
trait LowTransformSpec extends SimpleTransformSpec {
|
||||
def emitter = new LowFirrtlEmitter
|
||||
def transform: Transform
|
||||
def transforms: Seq[Transform] = Seq(
|
||||
new ChirrtlToHighFirrtl(),
|
||||
new IRToWorkingIR(),
|
||||
new ResolveAndCheck(),
|
||||
new HighFirrtlToMiddleFirrtl(),
|
||||
new MiddleFirrtlToLowFirrtl(),
|
||||
new CustomResolveAndCheck(LowForm),
|
||||
transform
|
||||
)
|
||||
}
|
||||
|
||||
trait MiddleTransformSpec extends SimpleTransformSpec {
|
||||
def emitter = new MiddleFirrtlEmitter
|
||||
def transform: Transform
|
||||
def transforms: Seq[Transform] = Seq(
|
||||
new ChirrtlToHighFirrtl(),
|
||||
new IRToWorkingIR(),
|
||||
new ResolveAndCheck(),
|
||||
new HighFirrtlToMiddleFirrtl(),
|
||||
new CustomResolveAndCheck(MidForm),
|
||||
transform
|
||||
)
|
||||
}
|
||||
|
||||
trait HighTransformSpec extends SimpleTransformSpec {
|
||||
def emitter = new HighFirrtlEmitter
|
||||
def transform: Transform
|
||||
def transforms = Seq(
|
||||
new ChirrtlToHighFirrtl(),
|
||||
new IRToWorkingIR(),
|
||||
new CustomResolveAndCheck(HighForm),
|
||||
transform
|
||||
)
|
||||
}
|
|
@ -3,11 +3,10 @@
|
|||
package midas.targetutils
|
||||
|
||||
import chisel3._
|
||||
import chisel3.experimental.{BaseModule, ChiselAnnotation}
|
||||
import chisel3.experimental.{BaseModule, ChiselAnnotation, annotate}
|
||||
|
||||
import firrtl.{RenameMap}
|
||||
import firrtl.annotations.{NoTargetAnnotation, SingleTargetAnnotation, ComponentName} // Deprecated
|
||||
import firrtl.annotations.{ReferenceTarget, ModuleTarget, AnnotationException}
|
||||
import firrtl.annotations._
|
||||
|
||||
// This is currently consumed by a transformation that runs after MIDAS's core
|
||||
// transformations In FireSim, targeting an F1 host, these are consumed by the
|
||||
|
@ -33,9 +32,11 @@ private[midas] class ReferenceTargetRenamer(renames: RenameMap) {
|
|||
// TODO: determine order for multiple renames, or just check of == 1 rename?
|
||||
def exactRename(rt: ReferenceTarget): ReferenceTarget = {
|
||||
val renameMatches = renames.get(rt).getOrElse(Seq(rt)).collect({ case rt: ReferenceTarget => rt })
|
||||
assert(renameMatches.length == 1)
|
||||
assert(renameMatches.length == 1,
|
||||
s"${rt} should be renamed exactly once. Suggested renames: ${renameMatches}")
|
||||
renameMatches.head
|
||||
}
|
||||
|
||||
def apply(rt: ReferenceTarget): Seq[ReferenceTarget] = {
|
||||
renames.get(rt).getOrElse(Seq(rt)).collect({ case rt: ReferenceTarget => rt })
|
||||
}
|
||||
|
@ -92,7 +93,51 @@ object SynthesizePrintf {
|
|||
// TODO: Accept a printable -> need to somehow get the format string from
|
||||
}
|
||||
|
||||
// This labels a target Mem so that it is extracted and replaced with a separate model
|
||||
|
||||
/**
|
||||
* A mixed-in ancestor trait for all FAME annotations, useful for type-casing.
|
||||
*/
|
||||
trait FAMEAnnotation {
|
||||
this: Annotation =>
|
||||
}
|
||||
|
||||
/**
|
||||
* This labels an instance so that it is extracted as a separate FAME model.
|
||||
*/
|
||||
case class FAMEModelAnnotation(target: BaseModule) extends chisel3.experimental.ChiselAnnotation {
|
||||
def toFirrtl: FirrtlFAMEModelAnnotation = {
|
||||
val parent = ModuleTarget(target.toNamed.circuit.name, target.parentModName)
|
||||
FirrtlFAMEModelAnnotation(parent.instOf(target.instanceName, target.name))
|
||||
}
|
||||
}
|
||||
|
||||
case class FirrtlFAMEModelAnnotation(
|
||||
target: InstanceTarget) extends SingleTargetAnnotation[InstanceTarget] with FAMEAnnotation {
|
||||
def targets = Seq(target)
|
||||
def duplicate(n: InstanceTarget) = this.copy(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* This specifies that the module should be automatically multi-threaded (Chisel annotator).
|
||||
*/
|
||||
case class EnableModelMultiThreadingAnnotation(target: BaseModule) extends chisel3.experimental.ChiselAnnotation {
|
||||
def toFirrtl: FirrtlEnableModelMultiThreadingAnnotation = {
|
||||
FirrtlEnableModelMultiThreadingAnnotation(target.toNamed.toTarget)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This specifies that the module should be automatically multi-threaded (FIRRTL annotation).
|
||||
*/
|
||||
case class FirrtlEnableModelMultiThreadingAnnotation(
|
||||
target: ModuleTarget) extends SingleTargetAnnotation[ModuleTarget] with FAMEAnnotation {
|
||||
def targets = Seq(target)
|
||||
def duplicate(n: ModuleTarget) = this.copy(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* This labels a target Mem so that it is extracted and replaced with a separate model.
|
||||
*/
|
||||
case class MemModelAnnotation[T <: chisel3.Data](target: chisel3.MemBase[T])
|
||||
extends chisel3.experimental.ChiselAnnotation {
|
||||
def toFirrtl = FirrtlMemModelAnnotation(target.toNamed.toTarget)
|
||||
|
@ -116,38 +161,177 @@ object ExcludeInstanceAsserts {
|
|||
}
|
||||
|
||||
|
||||
//AutoCounter annotations
|
||||
|
||||
case class AutoCounterCoverAnnotation(target: ReferenceTarget, label: String, message: String) extends
|
||||
SingleTargetAnnotation[ReferenceTarget] {
|
||||
def duplicate(n: ReferenceTarget) = this.copy(target = n)
|
||||
}
|
||||
|
||||
case class AutoCounterFirrtlAnnotation(target: ReferenceTarget, label: String, message: String) extends
|
||||
SingleTargetAnnotation[ReferenceTarget] {
|
||||
def duplicate(n: ReferenceTarget) = this.copy(target = n)
|
||||
/**
|
||||
* AutoCounter annotations. Do not emit the FIRRTL annotations unless you are
|
||||
* writing a target transformation, use the Chisel-side [[PerfCounter]] object
|
||||
* instead.
|
||||
*
|
||||
*/
|
||||
case class AutoCounterFirrtlAnnotation(
|
||||
target: ReferenceTarget,
|
||||
clock: ReferenceTarget,
|
||||
reset: ReferenceTarget,
|
||||
label: String,
|
||||
message: String,
|
||||
coverGenerated: Boolean = false)
|
||||
extends firrtl.annotations.Annotation {
|
||||
def update(renames: RenameMap): Seq[firrtl.annotations.Annotation] = {
|
||||
val renamer = new ReferenceTargetRenamer(renames)
|
||||
val renamedTarget = renamer.exactRename(target)
|
||||
val renamedClock = renamer.exactRename(clock)
|
||||
val renamedReset = renamer.exactRename(reset)
|
||||
Seq(this.copy(target = renamedTarget, clock = renamedClock, reset = renamedReset))
|
||||
}
|
||||
// The AutoCounter tranform will reject this annotation if it's not enclosed
|
||||
def shouldBeIncluded(modList: Seq[String]): Boolean = !coverGenerated || modList.contains(target.module)
|
||||
def enclosingModule(): String = target.module
|
||||
def enclosingModuleTarget(): ModuleTarget = ModuleTarget(target.circuit, enclosingModule)
|
||||
}
|
||||
|
||||
case class AutoCounterCoverModuleFirrtlAnnotation(target: ModuleTarget) extends
|
||||
SingleTargetAnnotation[ModuleTarget] {
|
||||
SingleTargetAnnotation[ModuleTarget] with FAMEAnnotation {
|
||||
def duplicate(n: ModuleTarget) = this.copy(target = n)
|
||||
}
|
||||
|
||||
import chisel3.experimental.ChiselAnnotation
|
||||
case class AutoCounterCoverModuleAnnotation(target: String) extends ChiselAnnotation {
|
||||
//TODO: fix the CircuitName arguemnt of ModuleTarget after chisel implements Target
|
||||
//It currently doesn't matter since the transform throws away the circuit name
|
||||
def toFirrtl = AutoCounterCoverModuleFirrtlAnnotation(ModuleTarget("",target))
|
||||
}
|
||||
|
||||
case class AutoCounterAnnotation(target: chisel3.Data, label: String, message: String) extends ChiselAnnotation {
|
||||
def toFirrtl = AutoCounterFirrtlAnnotation(target.toNamed.toTarget, label, message)
|
||||
object PerfCounter {
|
||||
/**
|
||||
* Annotates a Bool representing a target event (ex. L1 D$ miss) that
|
||||
* should be tracked by AutoCounter
|
||||
*
|
||||
* @param target The event
|
||||
*
|
||||
* @param clock The clock to which this event is sychronized.
|
||||
*
|
||||
* @param reset If the event is asserted while under the provide reset, it
|
||||
* is not counted. TODO: This should be made optional.
|
||||
*
|
||||
* @param label A verilog-friendly identifier for the event signal
|
||||
*
|
||||
* @param message A description of the event.
|
||||
*
|
||||
*/
|
||||
def apply(target: chisel3.Bool,
|
||||
clock: chisel3.Clock,
|
||||
reset: Reset,
|
||||
label: String,
|
||||
message: String): Unit = {
|
||||
dontTouch(reset)
|
||||
dontTouch(target)
|
||||
dontTouch(clock)
|
||||
annotate(new ChiselAnnotation {
|
||||
def toFirrtl = AutoCounterFirrtlAnnotation(target.toTarget, clock.toTarget,
|
||||
reset.toTarget, label, message)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A simplified variation of the full apply method above that uses the
|
||||
* implicit clock and reset.
|
||||
*/
|
||||
def apply(target: chisel3.Bool, label: String, message: String): Unit =
|
||||
apply(target, Module.clock, Module.reset, label, message)
|
||||
}
|
||||
|
||||
object PerfCounter {
|
||||
def apply(target: chisel3.Data, label: String, message: String): Unit = {
|
||||
chisel3.experimental.annotate(AutoCounterAnnotation(target, label, message))
|
||||
// Need serialization utils to be upstreamed to FIRRTL before i can use these.
|
||||
//sealed trait TriggerSourceType
|
||||
//case object Credit extends TriggerSourceType
|
||||
//case object Debit extends TriggerSourceType
|
||||
|
||||
case class TriggerSourceAnnotation(
|
||||
target: ReferenceTarget,
|
||||
clock: ReferenceTarget,
|
||||
reset: Option[ReferenceTarget],
|
||||
sourceType: Boolean) extends Annotation with FAMEAnnotation{
|
||||
def update(renames: RenameMap): Seq[firrtl.annotations.Annotation] = {
|
||||
val renamer = new ReferenceTargetRenamer(renames)
|
||||
val renamedTarget = renamer.exactRename(target)
|
||||
val renamedClock = renamer.exactRename(clock)
|
||||
val renamedReset = reset map renamer.exactRename
|
||||
Seq(this.copy(target = renamedTarget, clock = renamedClock, reset = renamedReset))
|
||||
}
|
||||
def enclosingModuleTarget(): ModuleTarget = ModuleTarget(target.circuit, target.module)
|
||||
def enclosingModule(): String = target.module
|
||||
}
|
||||
|
||||
|
||||
case class TriggerSinkAnnotation(
|
||||
target: ReferenceTarget,
|
||||
clock: ReferenceTarget) extends Annotation with FAMEAnnotation {
|
||||
def update(renames: RenameMap): Seq[firrtl.annotations.Annotation] = {
|
||||
val renamer = new ReferenceTargetRenamer(renames)
|
||||
val renamedTarget = renamer.exactRename(target)
|
||||
val renamedClock = renamer.exactRename(clock)
|
||||
Seq(this.copy(target = renamedTarget, clock = renamedClock))
|
||||
}
|
||||
def enclosingModuleTarget(): ModuleTarget = ModuleTarget(target.circuit, target.module)
|
||||
}
|
||||
|
||||
object TriggerSource {
|
||||
private def annotateTrigger(tpe: Boolean)(target: Bool, reset: Option[Bool]): Unit = {
|
||||
// Hack: Create dummy nodes until chisel-side instance annotations have been improved
|
||||
val clock = WireDefault(Module.clock)
|
||||
dontTouch(target)
|
||||
dontTouch(clock)
|
||||
reset.map(dontTouch.apply)
|
||||
annotate(new ChiselAnnotation {
|
||||
def toFirrtl = TriggerSourceAnnotation(target.toNamed.toTarget, clock.toNamed.toTarget, reset.map(_.toTarget), tpe)
|
||||
})
|
||||
}
|
||||
def annotateCredit = annotateTrigger(true) _
|
||||
def annotateDebit = annotateTrigger(false) _
|
||||
|
||||
/**
|
||||
* Methods to annotate a Boolean as a trigger credit or debit. Credits and
|
||||
* debits issued while the module's implicit reset is asserted are not
|
||||
* counted.
|
||||
*/
|
||||
def credit(credit: Bool): Unit = annotateCredit(credit, Some(Module.reset.toBool))
|
||||
def debit(debit: Bool): Unit = annotateDebit(debit, Some(Module.reset.toBool))
|
||||
def apply(creditSig: Bool, debitSig: Bool): Unit = {
|
||||
credit(creditSig)
|
||||
debit(debitSig)
|
||||
}
|
||||
|
||||
/**
|
||||
* Variations of the above methods that count credits and debits provided
|
||||
* while the implicit reset is asserted.
|
||||
*/
|
||||
def creditEvenUnderReset(credit: Bool): Unit = annotateCredit(credit, None)
|
||||
def debitEvenUnderReset(debit: Bool): Unit = annotateDebit(debit, None)
|
||||
def evenUnderReset(creditSig: Bool, debitSig: Bool): Unit = {
|
||||
creditEvenUnderReset(creditSig)
|
||||
debitEvenUnderReset(debitSig)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object TriggerSink {
|
||||
/**
|
||||
* Marks a bool as receiving the global trigger signal.
|
||||
*
|
||||
* @param target A Bool node that will be driven with the trigger
|
||||
*
|
||||
* @param noSourceDefault The value that the trigger signal should take on
|
||||
* if no trigger soruces are found in the target. This is a temporary parameter required
|
||||
* while this apply method generates a wire. Otherwise this can be punted to the target's RTL.
|
||||
*/
|
||||
def apply(target: Bool, noSourceDefault: =>Bool = true.B): Unit = {
|
||||
// Hack: Create dummy nodes until chisel-side instance annotations have been improved
|
||||
val targetWire = WireDefault(noSourceDefault)
|
||||
val clock = Module.clock
|
||||
target := targetWire
|
||||
dontTouch(targetWire)
|
||||
// Both the provided node and the generated one need to be dontTouched to stop
|
||||
// constProp from optimizing the down stream logic(?)
|
||||
dontTouch(target)
|
||||
dontTouch(clock)
|
||||
annotate(new ChiselAnnotation {
|
||||
def toFirrtl = TriggerSinkAnnotation(targetWire.toTarget, clock.toTarget)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,32 +49,6 @@ uint64_t host_mem_offset = -0x80000000LL;
|
|||
host_mem_offset += (1ULL << FASEDMEMORYTIMINGMODEL_0_target_addr_bits);
|
||||
#endif
|
||||
|
||||
// There can only be one instance of assert and print widgets as their IO is
|
||||
// uniquely generated by a FIRRTL transform
|
||||
#ifdef ASSERTIONBRIDGEMODULE_struct_guard
|
||||
#ifdef ASSERTIONBRIDGEMODULE_0_PRESENT
|
||||
ASSERTIONBRIDGEMODULE_0_substruct_create;
|
||||
add_bridge_driver(new synthesized_assertions_t(this, ASSERTIONBRIDGEMODULE_0_substruct));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef PRINTBRIDGEMODULE_struct_guard
|
||||
#ifdef PRINTBRIDGEMODULE_0_PRESENT
|
||||
PRINTBRIDGEMODULE_0_substruct_create;
|
||||
print_bridge = new synthesized_prints_t(this,
|
||||
args,
|
||||
PRINTBRIDGEMODULE_0_substruct,
|
||||
PRINTBRIDGEMODULE_0_print_count,
|
||||
PRINTBRIDGEMODULE_0_token_bytes,
|
||||
PRINTBRIDGEMODULE_0_idle_cycles_mask,
|
||||
PRINTBRIDGEMODULE_0_print_offsets,
|
||||
PRINTBRIDGEMODULE_0_format_strings,
|
||||
PRINTBRIDGEMODULE_0_argument_counts,
|
||||
PRINTBRIDGEMODULE_0_argument_widths,
|
||||
PRINTBRIDGEMODULE_0_DMA_ADDR);
|
||||
add_bridge_driver(print_bridge);
|
||||
#endif
|
||||
#endif
|
||||
// Add functions you'd like to periodically invoke on a paused simulator here.
|
||||
if (profile_interval != -1) {
|
||||
register_task([this](){ return this->profile_models();}, 0);
|
||||
|
|
|
@ -309,36 +309,52 @@ uint64_t host_mem_offset = -0x80000000LL;
|
|||
|
||||
#ifdef TRACERVBRIDGEMODULE_struct_guard
|
||||
#ifdef TRACERVBRIDGEMODULE_0_PRESENT
|
||||
TRACERVBRIDGEMODULE_0_substruct_create;
|
||||
add_bridge_driver(new tracerv_t(this, args, TRACERVBRIDGEMODULE_0_substruct, 0, TRACERVBRIDGEMODULE_0_DMA_ADDR));
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 0)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_1_PRESENT
|
||||
TRACERVBRIDGEMODULE_1_substruct_create;
|
||||
add_bridge_driver(new tracerv_t(this, args, TRACERVBRIDGEMODULE_1_substruct, 1, TRACERVBRIDGEMODULE_1_DMA_ADDR));
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 1)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_2_PRESENT
|
||||
TRACERVBRIDGEMODULE_2_substruct_create;
|
||||
add_bridge_driver(new tracerv_t(this, args, TRACERVBRIDGEMODULE_2_substruct, 2, TRACERVBRIDGEMODULE_2_DMA_ADDR));
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 2)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_3_PRESENT
|
||||
TRACERVBRIDGEMODULE_3_substruct_create;
|
||||
add_bridge_driver(new tracerv_t(this, args, TRACERVBRIDGEMODULE_3_substruct, 3, TRACERVBRIDGEMODULE_3_DMA_ADDR));
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 3)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_4_PRESENT
|
||||
TRACERVBRIDGEMODULE_4_substruct_create;
|
||||
add_bridge_driver(new tracerv_t(this, args, TRACERVBRIDGEMODULE_4_substruct, 4, TRACERVBRIDGEMODULE_4_DMA_ADDR));
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 4)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_5_PRESENT
|
||||
TRACERVBRIDGEMODULE_5_substruct_create;
|
||||
add_bridge_driver(new tracerv_t(this, args, TRACERVBRIDGEMODULE_5_substruct, 5, TRACERVBRIDGEMODULE_5_DMA_ADDR));
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 5)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_6_PRESENT
|
||||
TRACERVBRIDGEMODULE_6_substruct_create;
|
||||
add_bridge_driver(new tracerv_t(this, args, TRACERVBRIDGEMODULE_6_substruct, 6, TRACERVBRIDGEMODULE_6_DMA_ADDR));
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 6)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_7_PRESENT
|
||||
TRACERVBRIDGEMODULE_7_substruct_create;
|
||||
add_bridge_driver(new tracerv_t(this, args, TRACERVBRIDGEMODULE_7_substruct, 7, TRACERVBRIDGEMODULE_7_DMA_ADDR));
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 7)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_8_PRESENT
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 8)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_9_PRESENT
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 9)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_10_PRESENT
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 10)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_11_PRESENT
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 11)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_12_PRESENT
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 12)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_13_PRESENT
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 13)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_14_PRESENT
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 14)
|
||||
#endif
|
||||
#ifdef TRACERVBRIDGEMODULE_15_PRESENT
|
||||
INSTANTIATE_TRACERV(add_bridge_driver, 15)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
@ -385,117 +401,118 @@ uint64_t host_mem_offset = -0x80000000LL;
|
|||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_struct_guard
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_0_PRESENT
|
||||
AUTOCOUNTERBRIDGEMODULE_0_substruct_create;
|
||||
add_bridge_driver(new autocounter_t(
|
||||
this, args, AUTOCOUNTERBRIDGEMODULE_0_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_0_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_0_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_0_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_0_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_0_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_0_W_names), 0));
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_1_PRESENT
|
||||
AUTOCOUNTERBRIDGEMODULE_1_substruct_create;
|
||||
add_bridge_driver(new autocounter_t(
|
||||
this, args, AUTOCOUNTERBRIDGEMODULE_1_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_1_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_1_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_1_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_1_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_1_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_1_W_names), 1));
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_2_PRESENT
|
||||
AUTOCOUNTERBRIDGEMODULE_2_substruct_create;
|
||||
add_bridge_driver(new autocounter_t(
|
||||
this, args, AUTOCOUNTERBRIDGEMODULE_2_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_2_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_2_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_2_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_2_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_2_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_2_W_names), 2));
|
||||
#endif
|
||||
#ifdef AUTCOUNTERBRIDGEMODULE_3_PRESENT
|
||||
AUTOCOUNTERBRIDGEMODULE_3_substruct_create;
|
||||
add_bridge_driver(new autocounter_t(
|
||||
this, args, AUTOCOUNTERBRIDGEMODULE_3_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_3_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_3_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_3_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_3_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_3_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_3_W_names), 3));
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_4_PRESENT
|
||||
AUTOCOUNTERBRIDGEMODULE_4_substruct_create;
|
||||
add_bridge_driver(new autocounter_t(
|
||||
this, args, AUTOCOUNTERBRIDGEMODULE_4_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_4_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_4_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_4_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_4_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_4_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_4_W_names), 4));
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_5_PRESENT
|
||||
AUTOCOUNTERBRIDGEMODULE_5_substruct_create;
|
||||
add_bridge_driver(new autocounter_t(
|
||||
this, args, AUTOCOUNTERBRIDGEMODULE_5_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_5_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_5_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_5_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_5_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_5_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_5_W_names), 5));
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_6_PRESENT
|
||||
AUTOCOUNTERBRIDGEMODULE_6_substruct_create;
|
||||
add_bridge_driver(new autocounter_t(
|
||||
this, args, AUTOCOUNTERBRIDGEMODULE_6_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_6_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_6_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_6_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_6_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_6_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_6_W_names), 6));
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_7_PRESENT
|
||||
AUTOCOUNTERBRIDGEMODULE_7_substruct_create;
|
||||
add_bridge_driver(new autocounter_t(
|
||||
this, args, AUTOCOUNTERBRIDGEMODULE_7_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_7_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_7_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_7_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_7_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_7_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_7_W_names), 7));
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_0_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(add_bridge_driver, 0)
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_1_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(add_bridge_driver, 1)
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_2_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(add_bridge_driver, 2)
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_3_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(add_bridge_driver, 3)
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_4_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(add_bridge_driver, 4)
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_5_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(add_bridge_driver, 5)
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_6_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(add_bridge_driver, 6)
|
||||
#endif
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_7_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(add_bridge_driver, 7)
|
||||
#endif
|
||||
|
||||
// There can only be one instance of assert and print widgets as their IO is
|
||||
// uniquely generated by a FIRRTL transform
|
||||
#ifdef ASSERTBRIDGEMODULE_0_PRESENT
|
||||
ASSERTBRIDGEMODULE_0_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this, ASSERTBRIDGEMODULE_0_substruct));
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_0_substruct,
|
||||
ASSERTBRIDGEMODULE_0_assert_count,
|
||||
ASSERTBRIDGEMODULE_0_assert_messages));
|
||||
#endif
|
||||
#ifdef ASSERTBRIDGEMODULE_1_PRESENT
|
||||
ASSERTBRIDGEMODULE_1_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_1_substruct,
|
||||
ASSERTBRIDGEMODULE_1_assert_count,
|
||||
ASSERTBRIDGEMODULE_1_assert_messages));
|
||||
#endif
|
||||
#ifdef ASSERTBRIDGEMODULE_2_PRESENT
|
||||
ASSERTBRIDGEMODULE_2_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_2_substruct,
|
||||
ASSERTBRIDGEMODULE_2_assert_count,
|
||||
ASSERTBRIDGEMODULE_2_assert_messages));
|
||||
#endif
|
||||
#ifdef ASSERTBRIDGEMODULE_3_PRESENT
|
||||
ASSERTBRIDGEMODULE_3_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_3_substruct,
|
||||
ASSERTBRIDGEMODULE_3_assert_count,
|
||||
ASSERTBRIDGEMODULE_3_assert_messages));
|
||||
#endif
|
||||
#ifdef ASSERTBRIDGEMODULE_3_PRESENT
|
||||
ASSERTBRIDGEMODULE_3_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_3_substruct,
|
||||
ASSERTBRIDGEMODULE_3_assert_count,
|
||||
ASSERTBRIDGEMODULE_3_assert_messages));
|
||||
#endif
|
||||
#ifdef ASSERTBRIDGEMODULE_4_PRESENT
|
||||
ASSERTBRIDGEMODULE_4_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_4_substruct,
|
||||
ASSERTBRIDGEMODULE_4_assert_count,
|
||||
ASSERTBRIDGEMODULE_4_assert_messages));
|
||||
#endif
|
||||
#ifdef ASSERTBRIDGEMODULE_5_PRESENT
|
||||
ASSERTBRIDGEMODULE_5_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_5_substruct,
|
||||
ASSERTBRIDGEMODULE_5_assert_count,
|
||||
ASSERTBRIDGEMODULE_5_assert_messages));
|
||||
#endif
|
||||
#ifdef ASSERTBRIDGEMODULE_6_PRESENT
|
||||
ASSERTBRIDGEMODULE_6_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_6_substruct,
|
||||
ASSERTBRIDGEMODULE_6_assert_count,
|
||||
ASSERTBRIDGEMODULE_6_assert_messages));
|
||||
#endif
|
||||
#ifdef ASSERTBRIDGEMODULE_7_PRESENT
|
||||
ASSERTBRIDGEMODULE_7_substruct_create
|
||||
add_bridge_driver(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_7_substruct,
|
||||
ASSERTBRIDGEMODULE_7_assert_count,
|
||||
ASSERTBRIDGEMODULE_7_assert_messages));
|
||||
#endif
|
||||
|
||||
#ifdef PRINTBRIDGEMODULE_0_PRESENT
|
||||
PRINTBRIDGEMODULE_0_substruct_create;
|
||||
add_bridge_driver(new synthesized_prints_t(this,
|
||||
args,
|
||||
PRINTBRIDGEMODULE_0_substruct,
|
||||
PRINTBRIDGEMODULE_0_print_count,
|
||||
PRINTBRIDGEMODULE_0_token_bytes,
|
||||
PRINTBRIDGEMODULE_0_idle_cycles_mask,
|
||||
PRINTBRIDGEMODULE_0_print_offsets,
|
||||
PRINTBRIDGEMODULE_0_format_strings,
|
||||
PRINTBRIDGEMODULE_0_argument_counts,
|
||||
PRINTBRIDGEMODULE_0_argument_widths,
|
||||
PRINTBRIDGEMODULE_0_DMA_ADDR));
|
||||
INSTANTIATE_PRINTF(add_bridge_driver,0)
|
||||
#endif
|
||||
#ifdef PRINTBRIDGEMODULE_1_PRESENT
|
||||
INSTANTIATE_PRINTF(add_bridge_driver,1)
|
||||
#endif
|
||||
#ifdef PRINTBRIDGEMODULE_2_PRESENT
|
||||
INSTANTIATE_PRINTF(add_bridge_driver,2)
|
||||
#endif
|
||||
#ifdef PRINTBRIDGEMODULE_3_PRESENT
|
||||
INSTANTIATE_PRINTF(add_bridge_driver,3)
|
||||
#endif
|
||||
#ifdef PRINTBRIDGEMODULE_4_PRESENT
|
||||
INSTANTIATE_PRINTF(add_bridge_driver,4)
|
||||
#endif
|
||||
#ifdef PRINTBRIDGEMODULE_5_PRESENT
|
||||
INSTANTIATE_PRINTF(add_bridge_driver,5)
|
||||
#endif
|
||||
#ifdef PRINTBRIDGEMODULE_6_PRESENT
|
||||
INSTANTIATE_PRINTF(add_bridge_driver,6)
|
||||
#endif
|
||||
#ifdef PRINTBRIDGEMODULE_7_PRESENT
|
||||
INSTANTIATE_PRINTF(add_bridge_driver,7)
|
||||
#endif
|
||||
// Add functions you'd like to periodically invoke on a paused simulator here.
|
||||
if (profile_interval != -1) {
|
||||
|
@ -576,6 +593,7 @@ void firesim_top_t::run() {
|
|||
fprintf(stderr, "time elapsed: %.1f s, simulation speed = %.2f KHz\n", sim_time, sim_speed);
|
||||
}
|
||||
double fmr = ((double) hcycles / end_cycle);
|
||||
// This returns the FMR of the fastest target clock
|
||||
fprintf(stderr, "FPGA-Cycles-to-Model-Cycles Ratio (FMR): %.2f\n", fmr);
|
||||
expect(!exitcode, NULL);
|
||||
|
||||
|
|
|
@ -9,7 +9,10 @@ public:
|
|||
synthesized_assertions_t * assert_endpoint;
|
||||
AssertModule_t(int argc, char** argv) {
|
||||
ASSERTBRIDGEMODULE_0_substruct_create;
|
||||
assert_endpoint = new synthesized_assertions_t(this, ASSERTBRIDGEMODULE_0_substruct);
|
||||
assert_endpoint = new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_0_substruct,
|
||||
ASSERTBRIDGEMODULE_0_assert_count,
|
||||
ASSERTBRIDGEMODULE_0_assert_messages);
|
||||
};
|
||||
void run() {
|
||||
int assertions_thrown = 0;
|
||||
|
|
|
@ -11,7 +11,9 @@ class AutoCounterCoverModule_t: public autocounter_module_t, virtual simif_t
|
|||
public:
|
||||
AutoCounterCoverModule_t(int argc, char** argv): autocounter_module_t(argc, argv) {};
|
||||
virtual void run() {
|
||||
autocounter_endpoint->init();
|
||||
for (auto &autocounter_endpoint: autocounter_endpoints) {
|
||||
autocounter_endpoint->init();
|
||||
}
|
||||
poke(reset, 1);
|
||||
poke(io_a, 0);
|
||||
step(1);
|
||||
|
|
|
@ -8,26 +8,24 @@
|
|||
class autocounter_module_t: virtual simif_t
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<autocounter_t> autocounter_endpoint;
|
||||
std::vector<autocounter_t *> autocounter_endpoints;
|
||||
autocounter_module_t(int argc, char** argv) {
|
||||
AUTOCOUNTERBRIDGEMODULE_0_substruct_create;
|
||||
std::vector<std::string> args(argv + 1, argv + argc);
|
||||
autocounter_endpoint = std::unique_ptr<autocounter_t>(new autocounter_t(this,
|
||||
args,
|
||||
AUTOCOUNTERBRIDGEMODULE_0_substruct,
|
||||
AddressMap(AUTOCOUNTERBRIDGEMODULE_0_R_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_0_R_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_0_R_names,
|
||||
AUTOCOUNTERBRIDGEMODULE_0_W_num_registers,
|
||||
(const unsigned int*) AUTOCOUNTERBRIDGEMODULE_0_W_addrs,
|
||||
(const char* const*) AUTOCOUNTERBRIDGEMODULE_0_W_names), 0));
|
||||
INSTANTIATE_AUTOCOUNTER(autocounter_endpoints.push_back, 0)
|
||||
#ifdef AUTOCOUNTERBRIDGEMODULE_1_PRESENT
|
||||
INSTANTIATE_AUTOCOUNTER(autocounter_endpoints.push_back, 1)
|
||||
#endif
|
||||
};
|
||||
void run_and_collect(int cycles) {
|
||||
step(cycles, false);
|
||||
while (!done()) {
|
||||
autocounter_endpoint->tick();
|
||||
for (auto &autocounter_endpoint: autocounter_endpoints) {
|
||||
autocounter_endpoint->tick();
|
||||
}
|
||||
}
|
||||
for (auto &autocounter_endpoint: autocounter_endpoints) {
|
||||
autocounter_endpoint->finish();
|
||||
}
|
||||
autocounter_endpoint->finish();
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -37,7 +35,9 @@ class AutoCounterModule_t: public autocounter_module_t, virtual simif_t
|
|||
public:
|
||||
AutoCounterModule_t(int argc, char** argv): autocounter_module_t(argc, argv) {};
|
||||
virtual void run() {
|
||||
autocounter_endpoint->init();
|
||||
for (auto &autocounter_endpoint: autocounter_endpoints) {
|
||||
autocounter_endpoint->init();
|
||||
}
|
||||
poke(reset, 1);
|
||||
poke(io_a, 0);
|
||||
step(1);
|
||||
|
|
|
@ -33,14 +33,28 @@
|
|||
#include "PrintfModule.h"
|
||||
#elif defined DESIGNNAME_NarrowPrintfModule
|
||||
#include "NarrowPrintfModule.h"
|
||||
#elif defined DESIGNNAME_MulticlockPrintfModule
|
||||
#include "MulticlockPrintfModule.h"
|
||||
#elif defined DESIGNNAME_AutoCounterModule
|
||||
#include "AutoCounterModule.h"
|
||||
#elif defined DESIGNNAME_AutoCounterCoverModule
|
||||
#include "AutoCounterCoverModule.h"
|
||||
#elif defined DESIGNNAME_AutoCounterPrintfModule
|
||||
#include "PrintfModule.h"
|
||||
#elif defined DESIGNNAME_MulticlockAutoCounterModule
|
||||
#include "MulticlockAutoCounterModule.h"
|
||||
#elif defined DESIGNNAME_Accumulator
|
||||
#include "Accumulator.h"
|
||||
#elif defined DESIGNNAME_VerilogAccumulator
|
||||
#include "VerilogAccumulator.h"
|
||||
#elif defined DESIGNNAME_TrivialMulticlock
|
||||
#include "TrivialMulticlock.h"
|
||||
#elif defined DESIGNNAME_MulticlockAssertModule
|
||||
#include "MulticlockAssertModule.h"
|
||||
#elif defined DESIGNNAME_TriggerWiringModule
|
||||
#include "TriggerWiringModule.h"
|
||||
#elif defined DESIGNNAME_TwoAdders
|
||||
#include "TwoAdders.h"
|
||||
#endif
|
||||
|
||||
class dut_emul_t:
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
//See LICENSE for license details.
|
||||
|
||||
#include "simif.h"
|
||||
#include "bridges/synthesized_assertions.h"
|
||||
|
||||
class MulticlockAssertModule_t: virtual simif_t
|
||||
{
|
||||
public:
|
||||
std::vector<synthesized_assertions_t *> assert_endpoints;
|
||||
synthesized_assertions_t * full_rate_assert_ep;
|
||||
synthesized_assertions_t * half_rate_assert_ep;
|
||||
MulticlockAssertModule_t(int argc, char** argv) {
|
||||
ASSERTBRIDGEMODULE_0_substruct_create;
|
||||
ASSERTBRIDGEMODULE_1_substruct_create;
|
||||
full_rate_assert_ep = new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_0_substruct,
|
||||
ASSERTBRIDGEMODULE_0_assert_count,
|
||||
ASSERTBRIDGEMODULE_0_assert_messages);
|
||||
half_rate_assert_ep = new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_1_substruct,
|
||||
ASSERTBRIDGEMODULE_1_assert_count,
|
||||
ASSERTBRIDGEMODULE_1_assert_messages);
|
||||
assert_endpoints.push_back(full_rate_assert_ep);
|
||||
assert_endpoints.push_back(half_rate_assert_ep);
|
||||
};
|
||||
void run() {
|
||||
int assertions_thrown = 0;
|
||||
poke(reset, 0);
|
||||
poke(fullrate_pulseLength, 2);
|
||||
poke(fullrate_cycle, 186);
|
||||
poke(halfrate_pulseLength, 2);
|
||||
poke(halfrate_cycle, 129);
|
||||
step(256, false);
|
||||
while (!done()) {
|
||||
for (auto ep: assert_endpoints) {
|
||||
ep->tick();
|
||||
if (ep->terminate()) {
|
||||
ep->resume();
|
||||
assertions_thrown++;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(assertions_thrown == 3, "EXPECT: Two assertions thrown");
|
||||
};
|
||||
};
|
|
@ -0,0 +1,21 @@
|
|||
//See LICENSE for license details.
|
||||
|
||||
#include "AutoCounterModule.h"
|
||||
|
||||
#ifdef DESIGNNAME_MulticlockAutoCounterModule
|
||||
class MulticlockAutoCounterModule_t: public autocounter_module_t, virtual simif_t
|
||||
{
|
||||
public:
|
||||
MulticlockAutoCounterModule_t(int argc, char** argv): autocounter_module_t(argc, argv) {};
|
||||
virtual void run() {
|
||||
for (auto &autocounter_endpoint: autocounter_endpoints) {
|
||||
autocounter_endpoint->init();
|
||||
}
|
||||
poke(reset, 1);
|
||||
step(1);
|
||||
poke(reset, 0);
|
||||
run_and_collect(3000);
|
||||
};
|
||||
};
|
||||
#endif //DESIGNNAME_MulticlockAutoCounterModule
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
//See LICENSE for license details.
|
||||
|
||||
#include "PrintfModule.h"
|
||||
|
||||
#ifdef DESIGNNAME_MulticlockPrintfModule
|
||||
class MulticlockPrintfModule_t: public print_module_t, virtual simif_t
|
||||
{
|
||||
public:
|
||||
MulticlockPrintfModule_t(int argc, char** argv): print_module_t(argc, argv) {};
|
||||
virtual void run() {
|
||||
for (auto &print_endpoint: print_endpoints) {
|
||||
print_endpoint->init();
|
||||
}
|
||||
step(1);
|
||||
poke(reset, 0);
|
||||
run_and_collect_prints(256);
|
||||
};
|
||||
};
|
||||
#endif //DESIGNNAME_MulticlockPrintfModule
|
||||
|
|
@ -6,7 +6,9 @@ class NarrowPrintfModule_t: public print_module_t, virtual simif_t
|
|||
public:
|
||||
NarrowPrintfModule_t(int argc, char** argv): print_module_t(argc, argv) {};
|
||||
virtual void run() {
|
||||
print_endpoint->init();
|
||||
for (auto &print_endpoint: print_endpoints) {
|
||||
print_endpoint->init();
|
||||
}
|
||||
poke(reset, 1);
|
||||
poke(io_enable, 0);
|
||||
step(1);
|
||||
|
|
|
@ -7,30 +7,28 @@
|
|||
|
||||
class print_module_t: virtual simif_t
|
||||
{
|
||||
public:
|
||||
std::unique_ptr<synthesized_prints_t> print_endpoint;
|
||||
print_module_t(int argc, char** argv) {
|
||||
PRINTBRIDGEMODULE_0_substruct_create;
|
||||
std::vector<std::string> args(argv + 1, argv + argc);
|
||||
print_endpoint = std::unique_ptr<synthesized_prints_t>(new synthesized_prints_t(this,
|
||||
args,
|
||||
PRINTBRIDGEMODULE_0_substruct,
|
||||
PRINTBRIDGEMODULE_0_print_count,
|
||||
PRINTBRIDGEMODULE_0_token_bytes,
|
||||
PRINTBRIDGEMODULE_0_idle_cycles_mask,
|
||||
PRINTBRIDGEMODULE_0_print_offsets,
|
||||
PRINTBRIDGEMODULE_0_format_strings,
|
||||
PRINTBRIDGEMODULE_0_argument_counts,
|
||||
PRINTBRIDGEMODULE_0_argument_widths,
|
||||
PRINTBRIDGEMODULE_0_DMA_ADDR));
|
||||
};
|
||||
void run_and_collect_prints(int cycles) {
|
||||
step(cycles, false);
|
||||
while (!done()) {
|
||||
print_endpoint->tick();
|
||||
}
|
||||
print_endpoint->finish();
|
||||
};
|
||||
public:
|
||||
std::vector<synthesized_prints_t*> print_endpoints;
|
||||
print_module_t(int argc, char** argv) {
|
||||
std::vector<std::string> args(argv + 1, argv + argc);
|
||||
#ifdef PRINTBRIDGEMODULE_0_PRESENT
|
||||
INSTANTIATE_PRINTF(print_endpoints.push_back,0)
|
||||
#endif
|
||||
#ifdef PRINTBRIDGEMODULE_1_PRESENT
|
||||
INSTANTIATE_PRINTF(print_endpoints.push_back,1)
|
||||
#endif
|
||||
};
|
||||
void run_and_collect_prints(int cycles) {
|
||||
step(cycles, false);
|
||||
while (!done()) {
|
||||
for (auto &print_endpoint: print_endpoints) {
|
||||
print_endpoint->tick();
|
||||
}
|
||||
}
|
||||
for (auto &print_endpoint: print_endpoints) {
|
||||
print_endpoint->finish();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
#ifdef DESIGNNAME_PrintfModule
|
||||
|
@ -39,7 +37,9 @@ class PrintfModule_t: public print_module_t, virtual simif_t
|
|||
public:
|
||||
PrintfModule_t(int argc, char** argv): print_module_t(argc, argv) {};
|
||||
virtual void run() {
|
||||
print_endpoint->init();
|
||||
for (auto &print_endpoint: print_endpoints) {
|
||||
print_endpoint->init();
|
||||
}
|
||||
poke(reset, 1);
|
||||
poke(io_a, 0);
|
||||
poke(io_b, 0);
|
||||
|
@ -52,3 +52,23 @@ public:
|
|||
};
|
||||
};
|
||||
#endif //DESIGNNAME_PrintfModule
|
||||
|
||||
#ifdef DESIGNNAME_AutoCounterPrintfModule
|
||||
class AutoCounterPrintfModule_t: public print_module_t, virtual simif_t
|
||||
{
|
||||
public:
|
||||
AutoCounterPrintfModule_t(int argc, char** argv): print_module_t(argc, argv) {};
|
||||
virtual void run() {
|
||||
for (auto &print_endpoint: print_endpoints) {
|
||||
print_endpoint->init();
|
||||
}
|
||||
poke(reset, 1);
|
||||
poke(io_a, 0);
|
||||
step(1);
|
||||
poke(reset, 0);
|
||||
step(1);
|
||||
poke(io_a, 1);
|
||||
run_and_collect_prints(3000);
|
||||
};
|
||||
};
|
||||
#endif // DESIGNNAME_AutoCounterPrintf
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
//See LICENSE for license details.
|
||||
|
||||
#include "simif.h"
|
||||
#include "bridges/synthesized_assertions.h"
|
||||
|
||||
class TriggerWiringModule_t: virtual simif_t
|
||||
{
|
||||
public:
|
||||
std::vector<synthesized_assertions_t *> assert_endpoints;
|
||||
TriggerWiringModule_t(int argc, char** argv) {
|
||||
ASSERTBRIDGEMODULE_0_substruct_create;
|
||||
ASSERTBRIDGEMODULE_1_substruct_create;
|
||||
assert_endpoints.push_back(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_0_substruct,
|
||||
ASSERTBRIDGEMODULE_0_assert_count,
|
||||
ASSERTBRIDGEMODULE_0_assert_messages));
|
||||
assert_endpoints.push_back(new synthesized_assertions_t(this,
|
||||
ASSERTBRIDGEMODULE_1_substruct,
|
||||
ASSERTBRIDGEMODULE_1_assert_count,
|
||||
ASSERTBRIDGEMODULE_1_assert_messages));
|
||||
};
|
||||
bool simulation_complete() {
|
||||
bool is_complete = false;
|
||||
for (auto &e: assert_endpoints) {
|
||||
is_complete |= e->terminate();
|
||||
}
|
||||
return is_complete;
|
||||
}
|
||||
int exit_code(){
|
||||
for (auto &e: assert_endpoints) {
|
||||
if (e->exit_code())
|
||||
return e->exit_code();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
void run() {
|
||||
int assertions_thrown = 0;
|
||||
poke(reset, 1);
|
||||
step(1);
|
||||
poke(reset, 0);
|
||||
step(10000, false);
|
||||
while (!done() && !simulation_complete()) {
|
||||
for (auto ep: assert_endpoints) {
|
||||
ep->tick();
|
||||
}
|
||||
}
|
||||
expect(!exit_code(), "No assertions should be thrown");
|
||||
}
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
//See LICENSE for license details.
|
||||
|
||||
#include "simif.h"
|
||||
|
||||
class MulticlockChecker {
|
||||
public:
|
||||
simif_t * sim;
|
||||
uint32_t field_address;
|
||||
int numerator, denominator;
|
||||
int cycle = -1;
|
||||
uint32_t expected_value;
|
||||
uint32_t fast_domain_reg, slow_domain_reg, fast_domain_reg_out;
|
||||
|
||||
MulticlockChecker(simif_t * sim, uint32_t field_address, int numerator, int denominator):
|
||||
sim(sim), field_address(field_address), numerator(numerator), denominator(denominator) {};
|
||||
void expect_and_update(uint64_t poked_value){
|
||||
if (cycle > 1 ) sim->expect(field_address, fast_domain_reg_out);
|
||||
if (cycle < 1) {
|
||||
fast_domain_reg_out = slow_domain_reg;
|
||||
slow_domain_reg = fast_domain_reg;
|
||||
} else {
|
||||
fast_domain_reg_out = slow_domain_reg;
|
||||
if (((cycle * numerator) / denominator) > (((cycle - 1) * numerator)/ denominator)) {
|
||||
// TODO: Handle the case where numerator * cycle is not a multiple of the division
|
||||
//if (((cycle * numerator) % denominator) != 0) {
|
||||
// fast_domain_reg_out = slow_domain_reg;
|
||||
// slow_domain_reg = poked_value;
|
||||
//} else {
|
||||
slow_domain_reg = fast_domain_reg;
|
||||
//}
|
||||
}
|
||||
}
|
||||
fast_domain_reg = poked_value;
|
||||
cycle++;
|
||||
};
|
||||
};
|
||||
|
||||
class TrivialMulticlock_t: virtual simif_t
|
||||
{
|
||||
public:
|
||||
TrivialMulticlock_t(int argc, char** argv) {}
|
||||
void run() {
|
||||
uint64_t limit = 256;
|
||||
std::vector<MulticlockChecker*> checkers;
|
||||
checkers.push_back(new MulticlockChecker(this, halfOut, 1, 2));
|
||||
checkers.push_back(new MulticlockChecker(this, thirdOut, 1, 3));
|
||||
// Resolve bug in PeekPoke Bridge
|
||||
//checkers.push_back(new MulticlockChecker(this, threeSeventhsOut, 3, 7));
|
||||
|
||||
uint32_t current = rand_next(limit);
|
||||
for(int i = 1; i < 1024; i++){
|
||||
for(auto checker: checkers) checker->expect_and_update(i);
|
||||
poke(in, i);
|
||||
current = rand_next(limit);
|
||||
step(1);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
//See LICENSE for license details.
|
||||
|
||||
#include "simif.h"
|
||||
#include "stdio.h"
|
||||
|
||||
#define NTESTS 6
|
||||
|
||||
class TwoAdders_t: virtual simif_t
|
||||
{
|
||||
public:
|
||||
TwoAdders_t(int argc, char** argv) {}
|
||||
uint32_t i0[NTESTS] = { 4, 8, 13, 26, 19, 0 };
|
||||
uint32_t i1[NTESTS] = { 31, 11, 99, 27, 43, 0 };
|
||||
uint32_t i2[NTESTS] = { 28, 7, 30, 2, 88, 0 };
|
||||
uint32_t i3[NTESTS] = { 67, 29, 50, 80, 59, 0 };
|
||||
void run() {
|
||||
int i;
|
||||
target_reset();
|
||||
poke(io_i0, i0[0]);
|
||||
poke(io_i1, i1[0]);
|
||||
poke(io_i2, i2[0]);
|
||||
poke(io_i3, i3[0]);
|
||||
step(1);
|
||||
for (i = 1; i < NTESTS; i++) {
|
||||
poke(io_i0, i0[i]);
|
||||
poke(io_i1, i1[i]);
|
||||
poke(io_i2, i2[i]);
|
||||
poke(io_i3, i3[i]);
|
||||
step(1); // has latency of 1 cycle
|
||||
expect(io_o0, i0[i-1] + i1[i-1]);
|
||||
expect(io_o1, i2[i-1] + i3[i-1]);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -105,9 +105,9 @@ NET_MACADDR ?= $(shell printf '00:00:00:00:00:%02x' $$(($(NET_SLOT)+2)))
|
|||
nic_args = +shmemportname0=$(NET_SHMEMPORTNAME) +macaddr0=$(NET_MACADDR) \
|
||||
+niclog0=niclog$(NET_SLOT) +linklatency0=$(NET_LINK_LATENCY) \
|
||||
+netbw0=$(NET_BW) +netburst0=8 $(NET_LOOPBACK)
|
||||
tracer_args = +tracefile0=TRACEFILE0
|
||||
tracer_args = +tracefile=TRACEFILE
|
||||
blkdev_args = +blkdev-in-mem0=128 +blkdev-log0=blkdev-log$(NET_SLOT)
|
||||
autocounter_args = +autocounter-readrate0=1000 +autocounter-filename0=AUTOCOUNTERFILE0
|
||||
autocounter_args = +autocounter-readrate=1000 +autocounter-filename=AUTOCOUNTERFILE
|
||||
# Neglecting this +arg will make the simulator use the same step size as on the
|
||||
# FPGA. This will make ML simulation more closely match results seen on the
|
||||
# FPGA at the expense of dramatically increased target runtime
|
||||
|
|
|
@ -11,7 +11,7 @@ import freechips.rocketchip.config.Parameters
|
|||
|
||||
import junctions.{NastiKey, NastiParameters}
|
||||
import midas.models.{FASEDBridge, AXI4EdgeSummary, CompleteConfig}
|
||||
import midas.widgets.{PeekPokeBridge}
|
||||
import midas.widgets.{PeekPokeBridge, RationalClockBridge}
|
||||
|
||||
object AXI4Printf {
|
||||
def apply(axi4: AXI4Bundle): Unit = {
|
||||
|
@ -90,18 +90,18 @@ class AXI4FuzzerDUT(implicit p: Parameters) extends LazyModule with HasFuzzTarge
|
|||
}
|
||||
|
||||
class AXI4Fuzzer(implicit val p: Parameters) extends RawModule {
|
||||
val clock = IO(Input(Clock()))
|
||||
val reset = WireInit(false.B)
|
||||
|
||||
val clockBridge = Module(new RationalClockBridge())
|
||||
val clock = clockBridge.io.clocks(0)
|
||||
withClockAndReset(clock, reset) {
|
||||
val fuzzer = Module((LazyModule(new AXI4FuzzerDUT)).module)
|
||||
val nastiKey = NastiParameters(fuzzer.axi4.r.bits.data.getWidth,
|
||||
fuzzer.axi4.ar.bits.addr.getWidth,
|
||||
fuzzer.axi4.ar.bits.id.getWidth)
|
||||
|
||||
val fasedInstance = FASEDBridge(fuzzer.axi4, reset,
|
||||
val fasedInstance = FASEDBridge(clock, fuzzer.axi4, reset,
|
||||
CompleteConfig(p(firesim.configs.MemModelKey), nastiKey, Some(AXI4EdgeSummary(fuzzer.axi4Edge))))
|
||||
val peekPokeBridge = PeekPokeBridge(reset,
|
||||
val peekPokeBridge = PeekPokeBridge(clock, reset,
|
||||
("done", fuzzer.done),
|
||||
("error", fuzzer.error))
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package firesim.midasexamples
|
||||
|
||||
import chisel3._
|
||||
import midas.widgets.{RationalClockBridge, PeekPokeBridge, RationalClock}
|
||||
|
||||
class ChildModule extends Module {
|
||||
val io = IO(new Bundle {
|
||||
|
@ -10,7 +11,6 @@ class ChildModule extends Module {
|
|||
})
|
||||
assert(!io.pred, "Pred asserted")
|
||||
}
|
||||
|
||||
class AssertModuleDUT extends Module {
|
||||
val io = IO(new Bundle {
|
||||
val cycleToFail = Input(UInt(16.W))
|
||||
|
@ -31,3 +31,71 @@ class AssertModuleDUT extends Module {
|
|||
}
|
||||
|
||||
class AssertModule extends PeekPokeMidasExampleHarness(() => new AssertModuleDUT)
|
||||
|
||||
class RegisteredAssertModule extends Module {
|
||||
val io = IO(new Bundle {
|
||||
val pred = Input(Bool())
|
||||
})
|
||||
assert(!RegNext(io.pred), "Pred asserted")
|
||||
}
|
||||
|
||||
class DualClockModule extends Module {
|
||||
val io = IO(new Bundle {
|
||||
val clockB = Input(Clock())
|
||||
val a = Input(Bool())
|
||||
val b = Input(Bool())
|
||||
val c = Input(Bool())
|
||||
val d = Input(Bool())
|
||||
})
|
||||
|
||||
withClock(io.clockB) {
|
||||
assert(!RegNext(io.a), "io.a asserted")
|
||||
val modB = Module(new RegisteredAssertModule)
|
||||
modB.io.pred := io.b
|
||||
}
|
||||
|
||||
assert(!RegNext(io.c), "io.c asserted")
|
||||
val modA = Module(new RegisteredAssertModule)
|
||||
modA.io.pred := io.d
|
||||
}
|
||||
|
||||
class StimulusGenerator extends MultiIOModule {
|
||||
val input = IO(new Bundle {
|
||||
val cycle = Input(UInt(16.W))
|
||||
val pulseLength = Input(UInt(4.W))
|
||||
})
|
||||
val pred = IO(Output(Bool()))
|
||||
// Here i'm relying on zero-intialization of state instead of reset
|
||||
val cycleCount = Reg(UInt(16.W))
|
||||
cycleCount := cycleCount + 1.U
|
||||
|
||||
val pulseLengthRemaining = Reg(UInt(4.W))
|
||||
when(input.cycle === cycleCount && input.pulseLength =/= 0.U) {
|
||||
pulseLengthRemaining := input.pulseLength - 1.U
|
||||
}.elsewhen(pulseLengthRemaining =/= 0.U) {
|
||||
pulseLengthRemaining := pulseLengthRemaining - 1.U
|
||||
}
|
||||
|
||||
pred := pulseLengthRemaining =/= 0.U || input.cycle === cycleCount
|
||||
}
|
||||
|
||||
|
||||
class MulticlockAssertModule extends RawModule {
|
||||
val clockBridge = Module(new RationalClockBridge(RationalClock("HalfRate", 1, 2)))
|
||||
val List(refClock, div2Clock) = clockBridge.io.clocks.toList
|
||||
val reset = WireInit(false.B)
|
||||
withClockAndReset(refClock, reset) {
|
||||
val fullRateMod = Module(new RegisteredAssertModule)
|
||||
val fullRatePulseGen = Module(new StimulusGenerator)
|
||||
fullRateMod.io.pred := fullRatePulseGen.pred
|
||||
|
||||
val halfRateMod = Module(new RegisteredAssertModule)
|
||||
halfRateMod.clock := div2Clock
|
||||
val halfRatePulseGen = Module(new StimulusGenerator)
|
||||
halfRateMod.io.pred := halfRatePulseGen.pred
|
||||
|
||||
val peekPokeBridge = PeekPokeBridge(refClock, reset,
|
||||
("fullrate", fullRatePulseGen.input),
|
||||
("halfrate", halfRatePulseGen.input))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,10 @@ import chisel3.core.MultiIOModule
|
|||
import midas.targetutils.{PerfCounter, AutoCounterCoverModuleAnnotation}
|
||||
import freechips.rocketchip.util.property._
|
||||
|
||||
class AutoCounterModuleDUT extends Module {
|
||||
class AutoCounterModuleDUT(
|
||||
printfPrefix: String = "AUTOCOUNTER_PRINT ",
|
||||
instPath: String = "AutoCounterModule_AutoCounterModuleDUT",
|
||||
clockDivision: Int = 1) extends Module {
|
||||
val io = IO(new Bundle {
|
||||
val a = Input(Bool())
|
||||
})
|
||||
|
@ -29,18 +32,19 @@ class AutoCounterModuleDUT extends Module {
|
|||
|
||||
//--------VALIDATION---------------
|
||||
|
||||
val samplePeriod = 1000 / clockDivision
|
||||
val enabled_printcount = freechips.rocketchip.util.WideCounter(64, io.a)
|
||||
val enabled4_printcount = freechips.rocketchip.util.WideCounter(64, enabled4)
|
||||
val oddlfsr_printcount = freechips.rocketchip.util.WideCounter(64, childInst.io.oddlfsr)
|
||||
val cycle_print = Reg(UInt(64.W))
|
||||
cycle_print := cycle_print + 1.U
|
||||
when ((cycle_print >= 1000.U) & (cycle_print % 1000.U === 0.U)) {
|
||||
printf("AUTOCOUNTER_PRINT Cycle %d\n", cycle_print)
|
||||
printf("AUTOCOUNTER_PRINT ============================\n")
|
||||
printf("AUTOCOUNTER_PRINT PerfCounter ENABLED_AutoCounterModule_AutoCounterModuleDUT: %d\n", enabled_printcount)
|
||||
printf("AUTOCOUNTER_PRINT PerfCounter ENABLED_DIV_4_AutoCounterModule_AutoCounterModuleDUT: %d\n", enabled4_printcount)
|
||||
printf("AUTOCOUNTER_PRINT PerfCounter ODD_LFSR_AutoCounterModule_AutoCounterModuleDUT_childInst: %d\n", oddlfsr_printcount)
|
||||
printf("AUTOCOUNTER_PRINT \n")
|
||||
when ((cycle_print >= (samplePeriod - 1).U) & (cycle_print % samplePeriod.U === (samplePeriod - 1).U)) {
|
||||
printf(s"${printfPrefix}Cycle %d\n", cycle_print)
|
||||
printf(s"${printfPrefix}============================\n")
|
||||
printf(s"${printfPrefix}PerfCounter ENABLED_${instPath}: %d\n", enabled_printcount)
|
||||
printf(s"${printfPrefix}PerfCounter ENABLED_DIV_4_${instPath}: %d\n", enabled4_printcount)
|
||||
printf(s"${printfPrefix}PerfCounter ODD_LFSR_${instPath}_childInst: %d\n", oddlfsr_printcount)
|
||||
printf(s"${printfPrefix}\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,9 +84,10 @@ class AutoCounterCoverModuleDUT extends Module {
|
|||
when (cycle8) {
|
||||
cycle8_printcount := cycle8_printcount + 1.U
|
||||
}
|
||||
val samplePeriod = 1000
|
||||
val cycle_print = Reg(UInt(64.W))
|
||||
cycle_print := cycle_print + 1.U
|
||||
when ((cycle_print >= 1000.U) & (cycle_print % 1000.U === 0.U)) {
|
||||
when ((cycle_print >= (samplePeriod - 1).U) & (cycle_print % 1000.U === (samplePeriod - 1).U)) {
|
||||
printf("AUTOCOUNTER_PRINT Cycle %d\n", cycle_print)
|
||||
printf("AUTOCOUNTER_PRINT ============================\n")
|
||||
printf("AUTOCOUNTER_PRINT PerfCounter CYCLES_DIV_8_AutoCounterCoverModule_AutoCounterCoverModuleDUT: %d\n", cycle8_printcount)
|
||||
|
@ -93,3 +98,23 @@ class AutoCounterCoverModuleDUT extends Module {
|
|||
|
||||
class AutoCounterCoverModule extends PeekPokeMidasExampleHarness(() => new AutoCounterCoverModuleDUT)
|
||||
|
||||
class AutoCounterPrintfDUT extends Module {
|
||||
val io = IO(new Bundle {
|
||||
val a = Input(Bool())
|
||||
})
|
||||
|
||||
val childInst = Module(new AutoCounterModuleChild)
|
||||
childInst.io.c := io.a
|
||||
|
||||
//--------VALIDATION---------------
|
||||
|
||||
val oddlfsr_printcount = freechips.rocketchip.util.WideCounter(64, childInst.io.oddlfsr)
|
||||
val cycle_print = Reg(UInt(39.W))
|
||||
cycle_print := cycle_print + 1.U
|
||||
when (childInst.io.oddlfsr) {
|
||||
printf("SYNTHESIZED_PRINT CYCLE: %d [AutoCounter] ODD_LFSR: %d\n", cycle_print, oddlfsr_printcount)
|
||||
}
|
||||
}
|
||||
|
||||
class AutoCounterPrintfModule extends PeekPokeMidasExampleHarness(() => new AutoCounterPrintfDUT)
|
||||
|
||||
|
|
|
@ -13,11 +13,11 @@ import firesim.configs.WithDefaultMemModel
|
|||
class NoConfig extends Config(Parameters.empty)
|
||||
// This is incomplete and must be mixed into a complete platform config
|
||||
class DefaultF1Config extends Config(new Config((site, here, up) => {
|
||||
case DesiredHostFrequency => 75
|
||||
case SynthAsserts => true
|
||||
case midas.GenerateMultiCycleRamModels => true
|
||||
case SynthPrints => true
|
||||
case TargetTransforms => ((p: Parameters) => Seq(new midas.passes.AutoCounterTransform()(p))) +: up(TargetTransforms, site)
|
||||
case DesiredHostFrequency => 75
|
||||
case SynthAsserts => true
|
||||
case GenerateMultiCycleRamModels => true
|
||||
case SynthPrints => true
|
||||
case EnableAutoCounter => true
|
||||
}) ++ new Config(new firesim.configs.WithEC2F1Artefacts ++ new WithDefaultMemModel ++ new midas.F1Config))
|
||||
|
||||
class PointerChaserConfig extends Config((site, here, up) => {
|
||||
|
@ -28,3 +28,8 @@ class PointerChaserConfig extends Config((site, here, up) => {
|
|||
case NastiKey => NastiParameters(dataBits = 64, addrBits = 32, idBits = 3)
|
||||
case Seed => System.currentTimeMillis
|
||||
})
|
||||
|
||||
class AutoCounterPrintf extends Config((site, here, up) => {
|
||||
case AutoCounterUsePrintfImpl => true
|
||||
})
|
||||
|
||||
|
|
|
@ -4,18 +4,21 @@ package firesim.midasexamples
|
|||
|
||||
import chisel3._
|
||||
import chisel3.util.unless
|
||||
import chisel3.experimental.{withClock}
|
||||
import chisel3.experimental.{withClock, annotate}
|
||||
|
||||
import midas.widgets.PeekPokeBridge
|
||||
import midas.targetutils.FAMEModelAnnotation
|
||||
|
||||
class GCDDUT extends Module {
|
||||
val io = IO(new Bundle {
|
||||
val a = Input(UInt(16.W))
|
||||
val b = Input(UInt(16.W))
|
||||
val e = Input(Bool())
|
||||
val z = Output(UInt(16.W))
|
||||
val v = Output(Bool())
|
||||
})
|
||||
class GCDIO extends Bundle {
|
||||
val a = Input(UInt(16.W))
|
||||
val b = Input(UInt(16.W))
|
||||
val e = Input(Bool())
|
||||
val z = Output(UInt(16.W))
|
||||
val v = Output(Bool())
|
||||
}
|
||||
|
||||
class GCDInner extends Module {
|
||||
val io = IO(new GCDIO)
|
||||
val x = Reg(UInt())
|
||||
val y = Reg(UInt())
|
||||
when (x > y) { x := x - y }
|
||||
|
@ -23,9 +26,53 @@ class GCDDUT extends Module {
|
|||
when (io.e) { x := io.a; y := io.b }
|
||||
io.z := x
|
||||
io.v := y === 0.U
|
||||
|
||||
assert(!io.e || io.a =/= 0.U && io.b =/= 0.U, "Inputs to GCD cannot be 0")
|
||||
// TODO: this assertion fails spuriously with deduped, extracted models
|
||||
// assert(!io.e || io.a =/= 0.U && io.b =/= 0.U, "Inputs to GCD cannot be 0")
|
||||
printf("X: %d, Y:%d\n", x, y)
|
||||
}
|
||||
|
||||
class GCDDUT extends Module {
|
||||
val io = IO(new GCDIO)
|
||||
val inner1 = Module(new GCDInner)
|
||||
annotate(FAMEModelAnnotation(inner1))
|
||||
val inner2 = Module(new GCDInner)
|
||||
annotate(FAMEModelAnnotation(inner2))
|
||||
|
||||
val select = RegInit(false.B)
|
||||
select := !select
|
||||
|
||||
val done1 = RegInit(false.B)
|
||||
val result1 = Reg(UInt())
|
||||
|
||||
when (io.v) {
|
||||
done1 := false.B
|
||||
} .elsewhen (inner1.io.v) {
|
||||
done1 := true.B
|
||||
result1 := inner1.io.z
|
||||
}
|
||||
|
||||
val done2 = RegInit(false.B)
|
||||
val result2 = Reg(UInt())
|
||||
|
||||
when (io.v) {
|
||||
done2 := false.B
|
||||
} .elsewhen (inner2.io.v) {
|
||||
done2 := true.B
|
||||
result2 := inner2.io.z
|
||||
}
|
||||
|
||||
inner1.io.a := io.a
|
||||
inner1.io.b := io.b
|
||||
inner1.io.e := io.e
|
||||
|
||||
inner2.io.a := io.b
|
||||
inner2.io.b := io.a
|
||||
inner2.io.e := io.e
|
||||
|
||||
io.z := Mux(select, result1, result2)
|
||||
io.v := done1 && done2
|
||||
|
||||
assert(!done1 || !done2 || (result1 === result2), "Outputs do not match!")
|
||||
}
|
||||
|
||||
class GCD extends PeekPokeMidasExampleHarness(() => new GCDDUT)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue