Skip to content

Commit

Permalink
Refactoring and correctness improving
Browse files Browse the repository at this point in the history
  • Loading branch information
VonTum committed Oct 17, 2023
1 parent 46f57d5 commit 5cad090
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 137 deletions.
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,10 @@ I consider 'static pipelining' to be a solved problem. The one thing we can stil

An example of such static pipeline can be shown as follows:
```
pipeline multiply_add i32 a, i32 b, i32 c -> i32 result {
pipeline multiply_add : i32 a, i32 b, i32 c -> i32 result {
reg i32 tmp = a * b;
i32 tmp2 = tmp + c;
reg result = tmp + a;
reg result = tmp2 + a;
}
```
Pipeline stages are denoted by adding the 'reg' keyword to statements. Either at the statement level, or to add registers within expressions. This example would then compile to the following Verilog code:
Expand Down Expand Up @@ -253,19 +253,19 @@ Of course, connecting a data stream to a clock domain crossing without the prope
Rythms can be generated through built-in opaque modules.
```
rythmGenerator(clk*5, clk*3) :
[0]: () -> rythm v / v / v
[1]: () -> rythm v v v
left: () -> rythm v / v / v
right: () -> rythm v v v
```

These either use compile-time information from the tool that implements the clocks, or it generates a module that tests the clock domain crossing for the proper rythm at initialization time.

Delayed rythms follow a modular arithmetic. For example a rythm between clocks with a ratio of `rythm(clk*3,clk*5)`, will repeat every 5 clock cycles of the first clock, and 3 clock cycles of the second clock. `reg reg reg reg reg rythm(clk*3,clk*5)[0] = rythm(clk*3,clk*5)[0]`, `reg reg reg rythm(clk*3,clk*5)[1] = rythm(clk*3,clk*5)[1]`
Delayed rythms follow a modular arithmetic. For example a rythm between clocks with a ratio of `rythm(clk*3,clk*5)`, will repeat every 5 clock cycles of the first clock, and 3 clock cycles of the second clock. `reg reg reg reg reg rythm(clk*3,clk*5).left = rythm(clk*3,clk*5).left`, `reg reg reg rythm(clk*3,clk*5).right = rythm(clk*3,clk*5).right`

### Integrate Timing Constraints into language text itself
- False Paths
- Multicycle Paths

Often, false paths are used to denote constants that should be disseminated throughout the FPGA, or bits of hardware that won't affect each other, because only one will be active. Adding false paths relaxes the placement problem, leading to more optimal hardware implementations.
Often, false paths are used to denote semi-constants that should be disseminated throughout the FPGA, or bits of hardware that won't affect each other, because only one will be active. Adding false paths relaxes the placement problem, leading to more optimal hardware implementations for the paths that matter.

Constants specifically require that the modules the constant affect aren't being used when the constant changes. This should be representible in some way.

Expand Down Expand Up @@ -303,3 +303,6 @@ Constants specifically require that the modules the constant affect aren't being
- FIFO
- Ready/Acknowledge Clock domain Crossing
- Ring pipeline

## Long Term Strategy
[https://www.youtube.com/watch?v=XZ3w_jec1v8]("The Economics of Programming Languages" by Evan Czaplicki (Strange Loop 2023))
8 changes: 4 additions & 4 deletions philosophy/safety.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

So what does the Safety-First in Safety-First HDL mean? Like with our counterparts in Software Design such as Rust, it does not mean that the code you write is guaranteed to be correct. Rather it eliminates common classes of bugs that would otherwise have to be found through manual debugging. Counterintuitively however, is that the safety abstractions employed should never limit the programmer in the hardware they want to design. This means *any* hardware design one could possibly build in Verilog or VHDL, should also be representable in SUS. The difference should be that safe hardware should be easy to design, while unsafe should be comparatively difficult. Finally, as with Safe Software Languages, the goal is to enable fearless development and maintenance. The programmer should be able to rest easy that after implementing their change and fixing all compilation errors, the code again works properly.

## Common classes of HW bugs are:
### Cycle-wise timing errors through incorrectly pipelined HW.
## Common classes of HW bugs
### Cycle-wise timing errors through incorrectly pipelined HW
Manually keeping their pipeline in sync is taken out of the programmer's hands. The language makes a distinction between registers used for *latency* and those used for *state*. Latency registers are handled by latency counting and adding registers the other paths to keep them in sync.

### Misunderstood module documentation leading to incorrect use.
### Misunderstood module documentation leading to incorrect use
The system of Flow Descriptors is there to prevent incorrect use of library modules. Flow descriptors are not optional, so they force the programmer to add the proper descriptors when they define a module containing state.

### Operation results being cast to a too small integer bitwidth.
### Operation results being cast to a too small integer bitwidth
SUS disallows implicit casts that lose information. Instead, the programmer is required to specify either unsafe casts, where runtime checks can be inserted, or adding modular arithmetic to specify overflow behaviour.

### Data loss or duplication
Expand Down
2 changes: 1 addition & 1 deletion src/arena_alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ impl<'a, T, IndexMarker> IntoIterator for &'a mut ArenaVector<T, IndexMarker> {
}
}


#[derive(Debug)]
pub struct ListAllocator<T, IndexMarker> {
data : Vec<T>,
_ph : PhantomData<IndexMarker>
Expand Down
26 changes: 22 additions & 4 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use num_bigint::BigUint;

use crate::{tokenizer::TokenTypeIdx, linker::{ValueUUID, FileUUID}};
use core::ops::Range;
use std::ops::Deref;

// Token span. Indices are INCLUSIVE
#[derive(Clone,Copy,Debug,PartialEq,Eq)]
Expand Down Expand Up @@ -53,6 +54,17 @@ pub enum TypeExpression {
Array(Box<(SpanTypeExpression, SpanExpression)>)
}

impl TypeExpression {
pub fn get_root(&self) -> usize {
match self {
Self::Named(s) => *s,
Self::Array(b) => {
b.deref().0.0.get_root()
}
}
}
}

pub type SpanTypeExpression = (TypeExpression, Span);

#[derive(Debug,Clone)]
Expand Down Expand Up @@ -106,10 +118,16 @@ pub struct AssignableExpressionWithModifiers {
pub num_regs : u32
}

#[derive(Debug)]
pub struct CodeBlock {
pub statements : Vec<SpanStatement>
}

#[derive(Debug)]
pub enum Statement {
Declaration{local_id : usize},
Assign{to : Vec<AssignableExpressionWithModifiers>, eq_sign_position : Option<usize>, expr : SpanExpression}, // num_regs v = expr;
Block(Vec<SpanStatement>),
Block(CodeBlock),
TimelineStage(usize)
}

Expand All @@ -127,7 +145,7 @@ pub struct Module {
pub link_info : LinkInfo,

pub declarations : Vec<SignalDeclaration>,
pub code : Vec<SpanStatement>
pub code : CodeBlock
}

impl Module {
Expand Down Expand Up @@ -261,7 +279,7 @@ pub fn for_each_assign_in_block<F>(block : &Vec<SpanStatement>, func : &mut F) w
func(to, expr);
},
Statement::Block(b) => {
for_each_assign_in_block(b, func);
for_each_assign_in_block(&b.statements, func);
},
_other => {}
}
Expand All @@ -273,7 +291,7 @@ impl IterIdentifiers for Module {
for (pos, decl) in self.declarations.iter().enumerate() {
func(LocalOrGlobal::Local(pos), decl.span.1);
}
for_each_assign_in_block(&self.code, &mut |to, v| {
for_each_assign_in_block(&self.code.statements, &mut |to, v| {
for assign_to in to {
assign_to.expr.for_each_value(func);
}
Expand Down
8 changes: 4 additions & 4 deletions src/dev_aid/syntax_highlighting.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

use std::{ops::Range, path::PathBuf};

use crate::{ast::*, tokenizer::*, parser::*, linker::{PreLinker, FileData, Links, ValueUUID, NamedValue, Named, Linkable}, arena_alloc::ArenaVector};
use crate::{ast::*, tokenizer::*, parser::*, linker::{PreLinker, FileData, Links, ValueUUID, Named, Linkable}, arena_alloc::ArenaVector};

use ariadne::FileCache;
use console::Style;
Expand Down Expand Up @@ -101,8 +101,8 @@ fn add_ide_bracket_depths_recursive<'a>(result : &mut [IDEToken], current_depth
impl Named {
fn get_ide_type(&self) -> IDEIdentifierType{
match self {
Named::Value(NamedValue::Module(_)) => IDEIdentifierType::Interface,
Named::Value(NamedValue::Constant(_)) => IDEIdentifierType::Constant,
Named::Module(_) => IDEIdentifierType::Interface,
Named::Constant(_) => IDEIdentifierType::Constant,
Named::Type(_) => IDEIdentifierType::Type,
}
}
Expand All @@ -112,7 +112,7 @@ fn walk_name_color(all_objects : &[ValueUUID], links : &Links, result : &mut [ID
for obj_uuid in all_objects {
let object = &links.globals[*obj_uuid];
match object {
Named::Value(NamedValue::Module(module)) => {
Named::Module(module) => {
module.for_each_value(&mut |name, position| {
result[position].typ = IDETokenType::Identifier(if let LocalOrGlobal::Local(l) = name {
IDEIdentifierType::Value(module.declarations[l].identifier_type)
Expand Down
Loading

0 comments on commit 5cad090

Please sign in to comment.