lib: use DelayLine in ECP5_RAM

Cleans up the code nicely, and still produces the correct logic.
This commit is contained in:
David Anderson 2024-08-13 20:53:47 -07:00
parent f1e705fd31
commit 5df41d4b94
4 changed files with 59 additions and 51 deletions

View File

@ -5,8 +5,8 @@ import ECP5_RAM::*;
//(* always_enabled *) //(* always_enabled *)
interface Top; interface Top;
interface EBRPort#(Bit#(12), Bit#(4)) ram1; interface EBRPort#(Bit#(12), Bit#(4)) ram1;
//interface EBRPort#(Bit#(14), Bit#(1)) ram2; interface EBRPort#(Bit#(14), Bit#(1)) ram2;
interface EBRPort#(void, void) ram2; //interface EBRPort#(void, void) ram2;
endinterface endinterface
(* synthesize *) (* synthesize *)

View File

@ -14,6 +14,7 @@ import List::*;
// implicit condition, ready() can poll the delay line's output // implicit condition, ready() can poll the delay line's output
// without blocking. // without blocking.
interface DelayLine#(type value); interface DelayLine#(type value);
(* always_ready *)
method Action _write (value v); method Action _write (value v);
method value _read(); method value _read();
(* always_ready *) (* always_ready *)

View File

@ -6,6 +6,8 @@ import Printf::*;
import ToString::*; import ToString::*;
import StmtFSM::*; import StmtFSM::*;
import DelayLine::*;
export EBRWriteMode(..); export EBRWriteMode(..);
export EBRPortConfig(..); export EBRPortConfig(..);
export EBRPort(..); export EBRPort(..);
@ -494,62 +496,65 @@ module mkEBR#(EBRPortConfig cfgA,
let mem <- mkEBRCore(cfgA, cfgB); let mem <- mkEBRCore(cfgA, cfgB);
WriteOnly#(Bool) portA_start_op = ?; DelayLine#(void) latencyA <- mkDelayLine(rcfgA.operation_latency, clocked_by(rcfgA.clk), reset_by(rcfgA.rstN));
ReadOnly#(Bool) portA_op_complete = ?; DelayLine#(void) latencyB <- mkDelayLine(rcfgB.operation_latency, clocked_by(rcfgB.clk), reset_by(rcfgB.rstN));
WriteOnly#(Bool) portB_start_op = ?;
ReadOnly#(Bool) portB_op_complete = ?;
// TODO: this variable-depth register chain should be pulled into a // WriteOnly#(Bool) portA_start_op = ?;
// separate "delay line" module. // ReadOnly#(Bool) portA_op_complete = ?;
if (!rcfgA.enabled) begin // WriteOnly#(Bool) portB_start_op = ?;
portA_start_op = discardingWriteOnly; // ReadOnly#(Bool) portB_op_complete = ?;
portA_op_complete = constToReadOnly(False);
end
else if (rcfgA.register_output) begin
let syncA1 <- mkDReg(False, clocked_by(rcfgA.clk), reset_by(rcfgA.rstN));
let syncA2 <- mkReg(False, clocked_by(rcfgA.clk), reset_by(rcfgA.rstN));
portA_start_op = regToWriteOnly(syncA1);
portA_op_complete = regToReadOnly(syncA2);
(* no_implicit_conditions, fire_when_enabled *) // // TODO: this variable-depth register chain should be pulled into a
rule syncA1_to_syncA2; // // separate "delay line" module.
syncA2 <= syncA1; // if (!rcfgA.enabled) begin
endrule // portA_start_op = discardingWriteOnly;
end // portA_op_complete = constToReadOnly(False);
else begin // end
let syncA <- mkDReg(False, clocked_by(rcfgA.clk), reset_by(rcfgA.rstN)); // else if (rcfgA.register_output) begin
portA_start_op = regToWriteOnly(syncA); // let syncA1 <- mkDReg(False, clocked_by(rcfgA.clk), reset_by(rcfgA.rstN));
portA_op_complete = regToReadOnly(syncA); // let syncA2 <- mkReg(False, clocked_by(rcfgA.clk), reset_by(rcfgA.rstN));
end // portA_start_op = regToWriteOnly(syncA1);
// portA_op_complete = regToReadOnly(syncA2);
if (!rcfgB.enabled) begin // (* no_implicit_conditions, fire_when_enabled *)
portB_start_op = discardingWriteOnly; // rule syncA1_to_syncA2;
portB_op_complete = constToReadOnly(False); // syncA2 <= syncA1;
end // endrule
else if (rcfgB.register_output) begin // end
let syncB1 <- mkDReg(False, clocked_by(rcfgB.clk), reset_by(rcfgB.rstN)); // else begin
let syncB2 <- mkReg(False, clocked_by(rcfgB.clk), reset_by(rcfgB.rstN)); // let syncA <- mkDReg(False, clocked_by(rcfgA.clk), reset_by(rcfgA.rstN));
portB_start_op = regToWriteOnly(syncB1); // portA_start_op = regToWriteOnly(syncA);
portB_op_complete = regToReadOnly(syncB2); // portA_op_complete = regToReadOnly(syncA);
// end
(* no_implicit_conditions, fire_when_enabled *) // if (!rcfgB.enabled) begin
rule syncB1_to_syncB2; // portB_start_op = discardingWriteOnly;
syncB2 <= syncB1; // portB_op_complete = constToReadOnly(False);
endrule // end
end // else if (rcfgB.register_output) begin
else begin // let syncB1 <- mkDReg(False, clocked_by(rcfgB.clk), reset_by(rcfgB.rstN));
let syncB1 <- mkDReg(False, clocked_by(rcfgB.clk), reset_by(rcfgB.rstN)); // let syncB2 <- mkReg(False, clocked_by(rcfgB.clk), reset_by(rcfgB.rstN));
portB_start_op = regToWriteOnly(syncB1); // portB_start_op = regToWriteOnly(syncB1);
portB_op_complete = regToReadOnly(syncB1); // portB_op_complete = regToReadOnly(syncB2);
end
// (* no_implicit_conditions, fire_when_enabled *)
// rule syncB1_to_syncB2;
// syncB2 <= syncB1;
// endrule
// end
// else begin
// let syncB1 <- mkDReg(False, clocked_by(rcfgB.clk), reset_by(rcfgB.rstN));
// portB_start_op = regToWriteOnly(syncB1);
// portB_op_complete = regToReadOnly(syncB1);
// end
interface EBRPort portA; interface EBRPort portA;
method Action put(UInt#(3) chip_select, Bool write, addr_a address, data_a datain); method Action put(UInt#(3) chip_select, Bool write, addr_a address, data_a datain);
mem.portA.put(chip_select, write, address, datain); mem.portA.put(chip_select, write, address, datain);
if (rcfgA.write_outputs_data || !write) if (rcfgA.write_outputs_data || !write)
portA_start_op <= True; latencyA <= ?;
endmethod endmethod
method data_a read() if (rcfgA.enabled && portA_op_complete == True); method data_a read() if (rcfgA.enabled && latencyA.ready);
return mem.portA.read(); return mem.portA.read();
endmethod endmethod
endinterface endinterface
@ -558,9 +563,9 @@ module mkEBR#(EBRPortConfig cfgA,
method Action put(UInt#(3) chip_select, Bool write, addr_b address, data_b datain); method Action put(UInt#(3) chip_select, Bool write, addr_b address, data_b datain);
mem.portB.put(chip_select, write, address, datain); mem.portB.put(chip_select, write, address, datain);
if (rcfgB.write_outputs_data || !write) if (rcfgB.write_outputs_data || !write)
portB_start_op <= True; latencyB <= ?;
endmethod endmethod
method data_b read() if (rcfgB.enabled && portB_op_complete == True); method data_b read() if (rcfgB.enabled && latencyB.ready);
return mem.portB.read(); return mem.portB.read();
endmethod endmethod
endinterface endinterface

View File

@ -89,7 +89,7 @@ def build(c, target="."):
for target in expand_build_target(target): for target in expand_build_target(target):
out_info, out_verilog, out_bsc = ensure_build_dirs(target, "info", "verilog", "bsc") out_info, out_verilog, out_bsc = ensure_build_dirs(target, "info", "verilog", "bsc")
print(f"Building {target}") print(f"Building {target}")
c.run(f"bsc -aggressive-conditions -check-assert -u -verilog -info-dir {out_info} -vdir {out_verilog} -bdir {out_bsc} -p {target.parent}:lib:%/Libraries -show-module-use -show-compiles {target}") c.run(f"bsc -aggressive-conditions -check-assert -remove-dollar -remove-empty-rules -remove-false-rules -remove-starved-rules -remove-unused-modules -show-method-conf -show-method-bvi -u -verilog -info-dir {out_info} -vdir {out_verilog} -bdir {out_bsc} -p {target.parent}:lib:%/Libraries -show-module-use -show-compiles {target}")
module_name = Path(f"mk{target.stem}") module_name = Path(f"mk{target.stem}")
verilog_main_file = out_verilog / module_name.with_suffix(".v") verilog_main_file = out_verilog / module_name.with_suffix(".v")
@ -132,7 +132,9 @@ def synth(c, target):
hierarchy -top {module_name} hierarchy -top {module_name}
synth_ecp5 -top {module_name} -run :map_ram synth_ecp5 -top {module_name} -run :map_ram
write_verilog -sv {yosys_preprocessed} write_verilog -sv {yosys_preprocessed}
opt_clean -purge
synth_ecp5 -run map_ram: -json {yosys_json} synth_ecp5 -run map_ram: -json {yosys_json}
opt_clean -purge
write_verilog -sv {yosys_compiled} write_verilog -sv {yosys_compiled}
tee -o {yosys_report} stat tee -o {yosys_report} stat
""")) """))