Skip to content

Commit

Permalink
Can now use interfaces in function calls
Browse files Browse the repository at this point in the history
Also includes various fixes for interfaces in the LSP

TODO Latency Counting bug when connecting FIFO to itself?
  • Loading branch information
VonTum committed Jun 10, 2024
1 parent b040f5d commit fb6c5bd
Show file tree
Hide file tree
Showing 11 changed files with 247 additions and 141 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ If one is being pedantic, they actually shouldn't actually be called "languages"

- No hardware-specific language abstractions. Abstractions have to be built on top of the Object-Oriented or Functional basis of Scala. The regular scala constructs don't map to hardware, so instead functions have to be introduced (like `when()` for 'if') to imitate these but for hardware.
- Providing hardware-specific tooling for such languages is difficult. One can't hover over a variable in the code and get hardware-specific information from it, because the LSP is for Scala, not for Chisel. Also the Edit-Test-Debug cycle is longer, as there is no direct in-editor feedback for incorrect hardware.
- Finally, there is the philosophical question of: "Is the full feature set of a modern software language really necessary for hardware design?". Are Virtual Functions, Resource management, and other software features necessary to generate hardware? In practice, 99% of hardware generation code is simple for loops and conditionals. Anything more complicated than that shouldn't be happening on every compile.
- Finally, there is the philosophical question of: "Is the full feature set of a modern software language really necessary for hardware design?". Are Higher Order Functions, Dynamic Memory Allocation, or Object Oriented Programming necessary to generate hardware? In practice, 99% of hardware generation code is simple for loops and conditionals. Anything more complicated than that shouldn't be happening on every compile.

#### New Hardware *Design* Languages such as [TL-Verilog](https://arxiv.org/abs/1811.01780), [Spade](https://spade-lang.org/), [Filament](https://filamenthdl.com/), [RustHDL](https://rust-hdl.org/) and now [SUS](.)
The above opinions on the other styles of hardware design are shared by my colleagues building these new hardware *design* languages. The main differences between them are philosophical: What common hardware constructs and concepts should be abstracted and how?
Expand Down
11 changes: 9 additions & 2 deletions src/dev_aid/lsp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,17 +233,19 @@ fn gather_all_references_in_one_file(linker: &Linker, file_id: FileUUID, pos: us
fn gather_all_references_across_all_files(linker: &Linker, file_id: FileUUID, pos: usize) -> Vec<(FileUUID, Vec<Span>)> {
let mut ref_locations = Vec::new();

if let Some((_location, hover_info)) = get_selected_object(linker, file_id, pos) {
if let Some((location, hover_info)) = get_selected_object(linker, file_id, pos) {
let refers_to = RefersTo::from(hover_info);
if refers_to.is_global() {
for (other_file_id, other_file) in &linker.files {
let found_refs = gather_references_in_file(&linker, other_file, refers_to);
for r in &found_refs {assert!(location.size() == r.size())}
if found_refs.len() > 0 {
ref_locations.push((other_file_id, found_refs))
}
}
} else if let Some(local) = refers_to.local {
let found_refs = for_each_local_reference_in_module(&linker, local.0, local.1);
for r in &found_refs {assert!(location.size() == r.size())}
if found_refs.len() > 0 {
ref_locations.push((file_id, found_refs))
}
Expand Down Expand Up @@ -289,7 +291,7 @@ fn handle_request(method : &str, params : serde_json::Value, file_cache : &mut L
goto_definition_list.push((decl.name_span, md.link_info.file));
}
LocationInfo::InModule(_md_id, md, _decl_id, InModule::NamedSubmodule(submod_decl)) => {
goto_definition_list.push((submod_decl.module_name_span, md.link_info.file))
goto_definition_list.push((submod_decl.name.as_ref().unwrap().1, md.link_info.file))
}
LocationInfo::InModule(_, _, _, InModule::Temporary(_)) => {}
LocationInfo::Type(_) => {}
Expand Down Expand Up @@ -319,6 +321,7 @@ fn handle_request(method : &str, params : serde_json::Value, file_cache : &mut L
}
request::DocumentHighlightRequest::METHOD => {
let params : DocumentHighlightParams = serde_json::from_value(params).expect("JSON Encoding Error while parsing params");
println!("DocumentHighlight");

let (file_id, pos) = file_cache.location_in_file(&params.text_document_position_params);
let file_data = &file_cache.linker.files[file_id];
Expand All @@ -335,6 +338,7 @@ fn handle_request(method : &str, params : serde_json::Value, file_cache : &mut L
}
request::References::METHOD => {
let params : ReferenceParams = serde_json::from_value(params).expect("JSON Encoding Error while parsing params");
println!("FindAllReferences");

let (file_id, pos) = file_cache.location_in_file(&params.text_document_position);

Expand All @@ -344,6 +348,7 @@ fn handle_request(method : &str, params : serde_json::Value, file_cache : &mut L
}
request::Rename::METHOD => {
let params : RenameParams = serde_json::from_value(params).expect("JSON Encoding Error while parsing params");
println!("Rename");

let (file_id, pos) = file_cache.location_in_file(&params.text_document_position);

Expand All @@ -356,6 +361,8 @@ fn handle_request(method : &str, params : serde_json::Value, file_cache : &mut L
}).collect())
}).collect();

println!("{changes:?}");

serde_json::to_value(WorkspaceEdit{
changes : Some(changes),
document_changes: None,
Expand Down
39 changes: 25 additions & 14 deletions src/dev_aid/lsp/tree_walk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::ops::Deref;

use crate::{
file_position::Span, flattening::{Declaration, FlatID, Instruction, Interface, DomainID, Module, PortID, SubModuleInstance, WireInstance, WireReference, WireReferenceRoot, WireSource, WrittenType}, linker::{FileData, FileUUID, Linker, ModuleUUID, NameElem}
file_position::Span, flattening::{Declaration, DomainID, FlatID, Instruction, Interface, Module, ModuleInterfaceReference, PortID, SubModuleInstance, WireInstance, WireReference, WireReferenceRoot, WireSource, WrittenType}, linker::{FileData, FileUUID, Linker, ModuleUUID, NameElem}
};

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -44,10 +44,8 @@ impl<'linker> From<LocationInfo<'linker>> for RefersTo {
LocationInfo::InModule(md_id, md, flat_id, flat_obj) => {
match flat_obj {
InModule::NamedLocal(_) => {
for (port_id, port) in &md.ports {
if port.declaration_instruction == flat_id {
result.port = Some((md_id, port_id));
}
if let Some((port_id, _)) = md.ports.iter().find(|(_, port)| port.declaration_instruction == flat_id) {
result.port = Some((md_id, port_id));
}
result.local = Some((md_id, flat_id));
},
Expand Down Expand Up @@ -84,7 +82,7 @@ impl RefersTo {
}
}
pub fn is_global(&self) -> bool {
self.global.is_some() | self.port.is_some()
self.global.is_some() | self.port.is_some() | self.interface.is_some()
}
}

Expand Down Expand Up @@ -159,13 +157,15 @@ impl<'linker, Visitor : FnMut(Span, LocationInfo<'linker>), Pruner : Fn(Span) ->
self.visit(*span, LocationInfo::Global(NameElem::Constant(*cst)))
}
WireReferenceRoot::SubModulePort(port) => {
if let Some(submod_name_span) = port.submodule_name_span {
self.visit(submod_name_span, LocationInfo::InModule(md_id, md, port.submodule_flat, InModule::NamedSubmodule(md.instructions[port.submodule_flat].unwrap_submodule())));
}
if let Some(span) = port.port_name_span {
let sm_instruction = md.instructions[port.submodule_flat].unwrap_submodule();
let sm_instruction = md.instructions[port.submodule_decl].unwrap_submodule();
let submodule = &self.linker.modules[sm_instruction.module_uuid];
self.visit(span, LocationInfo::Port(sm_instruction, submodule, port.port))
self.visit(span, LocationInfo::Port(sm_instruction, submodule, port.port));

// port_name_span being enabled means submodule_name_span is for sure
// And if port_name_span is invalid, then submodule_name_span points to a duplicate!
// So in effect, port_name_span validity is a proxy for non-duplicate-ness of submodule_name_span
self.visit(port.submodule_name_span.unwrap(), LocationInfo::InModule(md_id, md, port.submodule_decl, InModule::NamedSubmodule(md.instructions[port.submodule_decl].unwrap_submodule())));
}
}
}
Expand All @@ -189,6 +189,19 @@ impl<'linker, Visitor : FnMut(Span, LocationInfo<'linker>), Pruner : Fn(Span) ->
}
}

fn walk_interface_reference(&mut self, md_id : ModuleUUID, md : &'linker Module, iref : &ModuleInterfaceReference) {
if let Some(submod_name_span) = iref.name_span {
let submodule_instruction = iref.submodule_decl;
let submodule = md.instructions[submodule_instruction].unwrap_submodule();
self.visit(submod_name_span, LocationInfo::InModule(md_id, md, submodule_instruction, InModule::NamedSubmodule(submodule)));
if iref.interface_span != submod_name_span {
let submod_md = &self.linker.modules[submodule.module_uuid];
let interface = &submod_md.interfaces[iref.submodule_interface];
self.visit(iref.interface_span, LocationInfo::Interface(submodule.module_uuid, submod_md, iref.submodule_interface, interface));
}
}
}

fn walk_module(&mut self, md_id : ModuleUUID) {
let md = &self.linker.modules[md_id];
if !(self.should_prune)(md.link_info.span) {
Expand Down Expand Up @@ -226,9 +239,7 @@ impl<'linker, Visitor : FnMut(Span, LocationInfo<'linker>), Pruner : Fn(Span) ->
self.walk_wire_ref(md_id, md, &write.to);
}
Instruction::FuncCall(fc) => {
if let Some(submod_name_span) = fc.name_span {
self.visit(submod_name_span, LocationInfo::InModule(md_id, md, fc.submodule_instruction, InModule::NamedSubmodule(md.instructions[fc.submodule_instruction].unwrap_submodule())));
}
self.walk_interface_reference(md_id, md, &fc.interface_reference);
}
Instruction::IfStatement(_) | Instruction::ForStatement(_) => {}
};
Expand Down
16 changes: 15 additions & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::cell::RefCell;

use crate::{arena_alloc::ArenaAllocator, file_position::{Span, SpanFile}, flattening::{Declaration, Module, Port, SubModuleInstance}, linker::{checkpoint::ErrorCheckpoint, FileData, FileUUID, FileUUIDMarker, LinkInfo}};
use crate::{arena_alloc::ArenaAllocator, file_position::{Span, SpanFile}, flattening::{Declaration, Interface, Module, Port, SubModuleInstance}, linker::{checkpoint::ErrorCheckpoint, FileData, FileUUID, FileUUIDMarker, LinkInfo}};

#[derive(Debug,Clone,PartialEq,Eq)]
pub enum ErrorLevel {
Expand Down Expand Up @@ -161,6 +161,12 @@ impl<'ec> ErrorReference<'ec> {
let (position, info) = obj.make_info(&self.err_collector.files[file]);
self.existing_info(ErrorInfo{ position, file, info })
}
pub fn suggest_replace<S : Into<String>>(&self, replace_span : Span, replace_with : S) -> &Self {
self.info_same_file(replace_span, format!("SUGGEST: Replace this with \"{}\"", replace_with.into()))
}
pub fn suggest_remove(&self, remove_span : Span) -> &Self {
self.info_same_file(remove_span, "SUGGEST: Remove this")
}
}

/// This represents objects that can be given as info to an error in a straight-forward way.
Expand Down Expand Up @@ -196,6 +202,14 @@ impl FileKnowingErrorInfoObject for LinkInfo {
}
}

/// For interfaces of this module
impl FileKnowingErrorInfoObject for (&'_ Module, &'_ Interface) {
fn make_global_info(&self, _files : &ArenaAllocator<FileData, FileUUIDMarker>) -> ((Span, FileUUID), String) {
let (md, interface) = *self;
((interface.name_span, md.link_info.file), format!("Interface '{}' defined here", &interface.name))
}
}

impl ErrorInfoObject for Port {
fn make_info(&self, _file_data : &FileData) -> (Span, String) {
(self.name_span, format!("Port '{}' declared here", &self.name))
Expand Down
4 changes: 4 additions & 0 deletions src/file_position.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ impl Span {
self.debug();
Span(self.0, self.0).debug()
}
pub fn empty_span_at_end(self) -> Span {
self.debug();
Span(self.1, self.1).debug()
}
}

impl PartialOrd for Span {
Expand Down
56 changes: 40 additions & 16 deletions src/flattening/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,22 @@ impl Module {
}

/// Get a port by the given name. Reports non existing ports errors
pub fn get_port_by_name(&self, name_span : Span, file_text : &FileText, errors : &ErrorCollector) -> Option<PortID> {
///
/// Prefer interfaces over ports in name conflicts
pub fn get_port_or_interface_by_name(&self, name_span : Span, file_text : &FileText, errors : &ErrorCollector) -> Option<PortOrInterface> {
let name_text = &file_text[name_span];
for (id, data) in &self.interfaces {
if data.name == name_text {
return Some(PortOrInterface::Interface(id))
}
}
for (id, data) in &self.ports {
if data.name == name_text {
return Some(id)
return Some(PortOrInterface::Port(id))
}
}
errors
.error(name_span, format!("There is no port '{name_text}' on module {}", self.link_info.name))
.error(name_span, format!("There is no port or interface of name '{name_text}' on module {}", self.link_info.name))
.info_obj(self);
return None
}
Expand Down Expand Up @@ -160,6 +167,12 @@ impl Module {
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PortOrInterface {
Port(PortID),
Interface(DomainID)
}

#[derive(Debug)]
pub struct DomainInfo {
pub name : String,
Expand Down Expand Up @@ -279,7 +292,7 @@ impl WireReferenceRoot {
match self {
WireReferenceRoot::LocalDecl(f, _) => Some(*f),
WireReferenceRoot::NamedConstant(_, _) => None,
WireReferenceRoot::SubModulePort(port) => Some(port.submodule_flat),
WireReferenceRoot::SubModulePort(port) => Some(port.submodule_decl),
}
}
#[track_caller]
Expand Down Expand Up @@ -354,13 +367,15 @@ pub enum BinaryOperator {

#[derive(Debug, Clone, Copy)]
pub struct PortInfo {
/// Even this can be implicit. In the inline function call instantiation syntax there's no named submodule. my_mod(a, b, c)
pub submodule_name_span : Option<Span>,
pub submodule_flat : FlatID,
pub submodule_decl : FlatID,
pub port : PortID,
pub port_identifier_typ : IdentifierType,
/// Only set if the port is named as an explicit field. If the port name is implicit, such as in the function call syntax, then it is not present.
pub port_name_span : Option<Span>,
pub port_identifier_typ : IdentifierType
/// Even this can be implicit. In the inline function call instantiation syntax there's no named submodule. my_mod(a, b, c)
///
/// Finally, if [Self::port_name_span].is_none(), then for highlighting and renaming, this points to a duplicate of a Function Call
pub submodule_name_span : Option<Span>
}

#[derive(Debug)]
Expand All @@ -386,7 +401,7 @@ impl WireSource {
match &wire_ref.root {
WireReferenceRoot::LocalDecl(decl_id, _) => func(*decl_id),
WireReferenceRoot::NamedConstant(_, _) => {}
WireReferenceRoot::SubModulePort(submod_port) => func(submod_port.submodule_flat),
WireReferenceRoot::SubModulePort(submod_port) => func(submod_port.submodule_decl),
}
for p in &wire_ref.path {
match p {
Expand Down Expand Up @@ -496,19 +511,28 @@ impl SubModuleInstance {
}

#[derive(Debug)]
pub struct FuncCallInstruction {
pub submodule_instruction : FlatID,
pub module_uuid : ModuleUUID,
pub struct ModuleInterfaceReference {
pub submodule_decl : FlatID,
pub submodule_interface : DomainID,

/// If this is None, that means the submodule was declared implicitly. Hence it could also be used at compiletime
pub name_span : Option<Span>,

/// Best-effort span for the interface that is called. [my_mod<abc>](), my_mod<abc> mm; [mm]() or mm.[my_interface]()
///
/// if interface_span == name_span then no specific interface is selected, so the main interface is used
pub interface_span : Span,
}

#[derive(Debug)]
pub struct FuncCallInstruction {
pub interface_reference : ModuleInterfaceReference,
/// arguments.len() == func_call_inputs.len() ALWAYS
pub arguments : Vec<FlatID>,
/// arguments.len() == func_call_inputs.len() ALWAYS
pub func_call_inputs : PortIDRange,
pub func_call_outputs : PortIDRange,
/// If this is None, that means the submodule was declared implicitly. Hence it could also be used at compiletime
pub name_span : Option<Span>,
/// Best-effort span for the interface that is called. [my_mod<abc>](), my_mod<abc> mm; [mm]() or mm.[my_interface]()
pub interface_span : Span,

pub arguments_span : BracketSpan,
pub whole_func_span : Span,
}
Expand Down
Loading

0 comments on commit fb6c5bd

Please sign in to comment.