Compare commits

..

7 Commits

Author SHA1 Message Date
David Anderson b0126a7d16 flake.nix: grab the GUI version of nextpnr 2024-09-13 21:25:01 -07:00
David Anderson a08fd421fe experiments/uart: wire up a top-level UART for ulx3s 2024-09-13 21:24:29 -07:00
David Anderson 379ebf0411 debugger/UART: implement a UART with RTS/CTS flow control
In practice the flow control is unusable on ULX3S dev boards because
the CTS line isn't hooked up (it's instead wired to JTAG_TDO, to enable
the USB<>UART chip to serve a dual purpose as a bitbanged JTAG programmer)

Still, support for flow control is nice, for the future. And the UART
itself also works regardless of flow control, which is of course nice.
2024-09-13 21:24:29 -07:00
David Anderson cea5fde170 tasks.py: when running without a full pin map, synth for 100MHz
It doesn't matter hugely, but by default nextpnr synthesizes for 12MHz,
which doesn't force it to work too hard on the placement. By requesting
100MHz, it needs to try a bit harder on timing and gives results that
are a bit closer to the fully constrained outcomes.
2024-09-13 11:41:12 -07:00
David Anderson 1b85c3e216 lib/Strobe: rewrite, using better math and some sad type hacking
The numeric types vs numeric value thing sucks, but there's a mild
workaround where you just recurse through numeric types until you
find one that matches the value you wanted. It's icky, but it ensures
registers are exactly the correct width instead of relying on later
synthesis to find and execute the width reduction.
2024-09-13 11:41:12 -07:00
David Anderson b527a62ab8 lib/PinSync: switch back to Reg type, annotate required timing
I was mostly using a separate interface to be able to mark the methods
always_enabled and always_ready, but you can attach those annotations
to the module constructor instead.
2024-09-13 10:45:39 -07:00
David Anderson d10a548bc1 lib/GlitchFilter: a glitch filter to debounce input pins 2024-09-12 18:11:21 -07:00
12 changed files with 831 additions and 53 deletions

243
debugger/UART.bsv Normal file
View File

@ -0,0 +1,243 @@
package UART;
import Cntrs::*;
import GetPut::*;
import FIFOF::*;
import SpecialFIFOs::*;
import StmtFSM::*;
import Connectable::*;
import PinSync::*;
import GlitchFilter::*;
import Strobe::*;
(* always_enabled *)
interface UART_RX_PHY;
(* prefix="" *)
method Action rx_in((* port="rx_in" *) bit b);
(* result="cts" *)
method Bool stop_sending();
endinterface
(* always_enabled *)
interface UART_TX_PHY;
(* result="tx_out" *)
method bit tx_out();
(* prefix="" *)
method Action can_send((* port="rts" *) Bool send);
endinterface
(* always_enabled *)
interface UART_PHY;
(* prefix="" *)
method Action rx_in((* port="rx_in" *) bit b);
(* result="tx_out" *)
method bit tx_out();
(* result="cts" *)
method Bool stop_sending();
(* prefix="" *)
method Action can_send((* port="rts" *) Bool send);
endinterface
interface UART_RX;
interface UART_RX_PHY phy;
interface Get#(Bit#(8)) receive;
endinterface
interface UART_TX;
interface UART_TX_PHY phy;
interface Put#(Bit#(8)) send;
endinterface
interface UART;
interface UART_PHY phy;
interface Put#(Bit#(8)) send;
interface Get#(Bit#(8)) receive;
endinterface
typedef enum {
WaitIdle,
Idle,
Read,
Stop,
Cork
} RXState deriving (Bits, Eq);
module mkUARTReceiver(Integer clock_frequency, Integer uart_bitrate, UART_RX ifc);
Reg#(bit) rx_sync <- mkPinSync(0);
let rx_in <- mkGlitchFilter(3, 0);
mkConnection(toGet(asReg(rx_sync)), toPut(asReg(rx_in)));
Reg#(RXState) rx_state <- mkReg(WaitIdle);
Strobe bit_16x_strobe <- mkStrobe(clock_frequency, 16*uart_bitrate);
Count#(UInt#(4)) cnt <- mkCount(0);
Reg#(Bit#(8)) shift_in <- mkReg(0);
FIFOF#(Bit#(8)) rx <- mkBypassFIFOF();
(* no_implicit_conditions, fire_when_enabled *)
rule rx_counter (bit_16x_strobe);
cnt.incr(1);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule rx_wait_idle (rx_state == WaitIdle && bit_16x_strobe && rx_in == 1);
rx_state <= Idle;
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule rx_idle (rx_state == Idle && bit_16x_strobe && rx_in == 0);
rx_state <= Read;
cnt <= 1;
shift_in <= 8'hFF;
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule rx_read (rx_state == Read && bit_16x_strobe && cnt == 7);
let shifted_out = shift_in[0];
shift_in <= {rx_in, shift_in[7:1]};
if (shifted_out == 0) begin // start bit reached end of shiftreg
rx_state <= Stop;
end
endrule
(* fire_when_enabled *)
rule rx_stop (rx_state == Stop);
if (rx.notFull) begin
rx.enq(shift_in);
rx_state <= WaitIdle;
end
else
rx_state <= Cork;
endrule
(* fire_when_enabled *)
rule rx_cork (rx_state == Cork);
rx.enq(shift_in);
rx_state <= WaitIdle;
endrule
interface UART_RX_PHY phy;
method rx_in = rx_sync._write;
method Bool stop_sending();
// Signal is active low, so 1 == "stop sending"
return rx_state == Cork;
endmethod
endinterface
interface receive = toGet(rx);
endmodule
typedef enum {
Idle,
Ready,
Send
} TXState deriving (Bits, Eq);
module mkUARTTransmitter(Integer clock_frequency, Integer uart_bitrate, UART_TX ifc);
Reg#(bit) cts_sync <- mkPinSync(0);
let cts_in <- mkGlitchFilter(3, 0);
mkConnection(toGet(asReg(cts_sync)), toPut(asReg(cts_in)));
Reg#(TXState) tx_state <- mkReg(Idle);
Strobe bit_strobe <- mkStrobe(clock_frequency, uart_bitrate);
Reg#(UInt#(4)) cnt <- mkReg(0);
Reg#(Bit#(9)) shift_out <- mkReg(9'h1FF);
FIFOF#(Bit#(8)) tx <- mkPipelineFIFOF();
(* fire_when_enabled *)
rule tx_idle (tx_state == Idle && tx.notEmpty);
shift_out <= {tx.first, 0};
tx.deq();
tx_state <= Ready;
bit_strobe.reset();
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule rx_ready (tx_state == Ready && bit_strobe && cts_in == 1);
tx_state <= Send;
cnt <= 0;
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule tx_send (tx_state == Send && bit_strobe);
shift_out <= {1'b1, shift_out[8:1]};
cnt <= cnt+1;
if (cnt == 9)
tx_state <= Idle;
endrule
interface UART_TX_PHY phy;
method bit tx_out();
if (tx_state == Send)
return shift_out[0];
else
return 1'b1;
endmethod
method Action can_send(b);
cts_sync <= pack(b);
endmethod
endinterface
interface Put send = toPut(tx);
endmodule
module mkUART(Integer clock_frequency, Integer uart_bitrate, UART ifc);
let _rx <- mkUARTReceiver(clock_frequency, uart_bitrate);
let _tx <- mkUARTTransmitter(clock_frequency, uart_bitrate);
interface UART_PHY phy;
method rx_in = _rx.phy.rx_in;
method tx_out = _tx.phy.tx_out;
method stop_sending = _rx.phy.stop_sending;
method can_send = _tx.phy.can_send;
endinterface
interface send = _tx.send;
interface receive = _rx.receive;
endmodule
typeclass FlowControlled#(type ifc);
module disableFlowControl(ifc i, Empty ret);
endtypeclass
instance FlowControlled#(UART_RX_PHY);
module disableFlowControl(UART_RX_PHY phy, Empty ifc);
endmodule
endinstance
instance FlowControlled#(UART_TX_PHY);
module disableFlowControl(UART_TX_PHY phy, Empty ifc);
(* no_implicit_conditions,fire_when_enabled *)
rule always_send;
phy.can_send(True);
endrule
endmodule
endinstance
instance FlowControlled#(UART_PHY);
module disableFlowControl(UART_PHY phy, Empty ifc);
(* no_implicit_conditions,fire_when_enabled *)
rule always_send;
phy.can_send(True);
endrule
endmodule
endinstance
instance FlowControlled#(UART_RX);
module disableFlowControl(UART_RX rx, Empty ifc);
disableFlowControl(rx.phy);
endmodule
endinstance
instance FlowControlled#(UART_TX);
module disableFlowControl(UART_TX tx, Empty ifc);
disableFlowControl(tx.phy);
endmodule
endinstance
instance FlowControlled#(UART);
module disableFlowControl(UART uart, Empty ifc);
disableFlowControl(uart.phy);
endmodule
endinstance
endpackage

265
debugger/UART_Test.bsv Normal file
View File

@ -0,0 +1,265 @@
package UART_Test;
import Assert::*;
import StmtFSM::*;
import Connectable::*;
import GetPut::*;
import Probe::*;
import UART::*;
import Testing::*;
import Strobe::*;
interface Test;
method Action start();
method Bool done();
endinterface
module mkTestReceiver(Test);
Reg#(Bool) running <- mkReg(False);
let testflags <- mkTestFlags();
let cycles <- mkCycleCounter();
let dut <- mkUARTReceiver(25_000_000, 115_200);
Reg#(bit) tx <- mkReg(1);
mkConnection(toGet(asReg(tx)), toPut(dut.phy.rx_in));
Probe#(Bit#(8)) read_probe <- mkProbe();
Probe#(Bool) cork_probe <- mkProbe();
rule record_cork;
cork_probe <= dut.phy.stop_sending();
endrule
Reg#(Bit#(40)) shift[2] <- mkCReg(2, 40'hFFFFFFFFFF);
let shift_in_strobe <- mkStrobe(25_000_000, 115_200);
(* no_implicit_conditions, fire_when_enabled *)
rule shift_in (running && shift_in_strobe && (tx == 0 || !dut.phy.stop_sending));
if (testflags.verbose)
$display("%0d (%0d): to UART: %0d", cycles.all, cycles, shift[0][0]);
tx <= shift[0][0];
shift[0] <= {1'b1, shift[0][39:1]};
endrule
function Action start_tx(Bit#(40) val);
return action
shift[1] <= val;
endaction;
endfunction
function Bool tx_idle();
return shift[0] == 40'hFFFFFFFFFF;
endfunction
function Bool tx_is(Bit#(40) val);
return shift[0] == val;
endfunction
let fsm <- mkFSM(seq
running <= True;
// Let UART initialize and settle
repeat (61) noAction;
// desynchronize sender and receiver strobes, to emulate a
// real setup.
shift_in_strobe.reset();
action
dynamicAssert(tx_idle(), "transmitter not idle");
dynamicAssert(!dut.phy.stop_sending(), "receiver not ready to receive");
endaction
// Single byte transmission
action
start_tx({10'h3FF, 10'h3FF, 10'h3FF, 1'b1, 8'd77, 1'b0});
cycles.reset();
endaction
// Read received byte
action
let got <- dut.receive.get();
read_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 77);
dynamicAssert(got == 77, "wrong byte received");
dynamicAssert(cycles < 2100, "byte not received during stop bit");
endaction
dynamicAssert(!dut.phy.stop_sending(), "receiver not ready to receive");
await(shift_in_strobe);
action
dynamicAssert(tx_idle(), "transmitter not idle");
dynamicAssert(!dut.phy.stop_sending(), "receiver not ready to receive");
endaction
await(shift_in_strobe);
// Send 4 bytes back to back, to check flow control
action
start_tx({
1'b1, 8'd25, 1'b0,
1'b1, 8'd14, 1'b0,
1'b1, 8'd59, 1'b0,
1'b1, 8'd42, 1'b0});
cycles.reset();
endaction
// Once two bytes are sent, the receiver should cork.
await(tx_is({10'h3FF, 10'h3FF, 1'b1, 8'd25, 1'b0, 1'b1, 8'd14, 1'b0}));
repeat (1000) noAction;
dynamicAssert(dut.phy.stop_sending(), "receiver did not cork sender");
// Read out one byte, verify that one more byte can transmit.
action
let got <- dut.receive.get();
read_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 42);
dynamicAssert(got == 42, "wrong byte received");
dynamicAssert(cycles < 5400, "byte not received during stop bit");
endaction
await(tx_is({10'h3FF, 10'h3FF, 10'h3FF, 1'b1, 8'd25, 1'b0}));
dynamicAssert(dut.phy.stop_sending(), "receiver did not cork sender");
// Read out one more byte, check the final byte transmits.
action
let got <- dut.receive.get();
read_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 59);
dynamicAssert(got == 59, "wrong byte received");
dynamicAssert(cycles < 7600, "byte not received during stop bit");
endaction
await(tx_is(40'hFFFFFFFFFF));
repeat (1000) noAction;
dynamicAssert(dut.phy.stop_sending(), "receiver did not cork sender");
// Read the final two bytes from the receiver.
action
let got <- dut.receive.get();
read_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 14);
dynamicAssert(got == 14, "wrong byte received");
dynamicAssert(cycles < 10500, "byte not received during stop bit");
endaction
repeat (1000) noAction;
dynamicAssert(!dut.phy.stop_sending(), "receiver did not uncork sender");
action
let got <- dut.receive.get();
read_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): UART.rx = %0d, want %0d", cycles.all, cycles, got, 25);
dynamicAssert(got == 25, "wrong byte received");
dynamicAssert(cycles < 11500, "byte not received during stop bit");
endaction
repeat (1000) noAction;
dynamicAssert(!dut.phy.stop_sending(), "receiver did not uncork sender");
running <= False;
endseq);
method start = fsm.start;
method done = fsm.done;
endmodule
module mkTestTransmitter(Test);
let testflags <- mkTestFlags();
let cycles <- mkCycleCounter();
let receiver <- mkUARTReceiver(25_000_000, 115_200);
let dut <- mkUARTTransmitter(25_000_000, 115_200);
mkConnection(toGet(dut.phy.tx_out), toPut(receiver.phy.rx_in));
mkConnection(toGet(True), toPut(dut.phy.can_send));
Probe#(bit) tx_probe <- mkProbe();
Probe#(Bit#(8)) recv_probe <- mkProbe();
rule record_tx;
tx_probe <= dut.phy.tx_out;
endrule
let fsm <- mkFSM(seq
// Let UART initialize and settle
repeat (61) noAction;
action
dut.send.put(42);
cycles.reset();
if (testflags.verbose)
$display("%0d: send", cycles.all);
endaction
action
let got <- receiver.receive.get();
recv_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
dynamicAssert(got == 42, "wrong value received");
dynamicAssert(cycles < 2000, "value received too late");
endaction
repeat(1000) noAction;
par
seq
dut.send.put(1);
dut.send.put(2);
dut.send.put(5);
dut.send.put(3);
dut.send.put(4);
endseq
seq
action
let got <- receiver.receive.get();
recv_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
dynamicAssert(got == 1, "wrong byte received");
endaction
action
let got <- receiver.receive.get();
recv_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
dynamicAssert(got == 2, "wrong byte received");
endaction
action
let got <- receiver.receive.get();
recv_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
dynamicAssert(got == 5, "wrong byte received");
endaction
action
let got <- receiver.receive.get();
recv_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
dynamicAssert(got == 3, "wrong byte received");
endaction
action
let got <- receiver.receive.get();
recv_probe <= got;
if (testflags.verbose)
$display("%0d (%0d): received %0d", cycles.all, cycles, got);
dynamicAssert(got == 4, "wrong byte received");
endaction
endseq
endpar
endseq);
method start = fsm.start;
method done = fsm.done;
endmodule
module mkTB();
let rx_test <- mkTestReceiver();
let tx_test <- mkTestTransmitter();
runTest(30000,
mkTest("UART", seq
rx_test.start();
await(rx_test.done);
tx_test.start();
await(tx_test.done);
endseq));
endmodule
endpackage

33
experiments/uart/Top.bsv Normal file
View File

@ -0,0 +1,33 @@
package Top;
import GetPut::*;
import UART::*;
(* always_enabled *)
interface Top;
(* prefix="" *)
method Action rx_in((* port="rx_in" *) bit b);
method bit tx_out();
method Bit#(8) leds();
endinterface
(* synthesize *)
module mkTop(Top);
let _ret <- mkUART(100_000_000, 115_200);
disableFlowControl(_ret);
Reg#(Bit#(8)) leds_out <- mkReg(0);
rule recv;
let b <- _ret.receive.get();
_ret.send.put(b);
leds_out <= b;
endrule
method rx_in = _ret.phy.rx_in;
method tx_out = _ret.phy.tx_out;
method leds = leds_out._read;
endmodule
endpackage

View File

@ -0,0 +1,33 @@
BLOCK RESETPATHS;
BLOCK ASYNCPATHS;
LOCATE COMP "CLK" SITE "G2";
IOBUF PORT "CLK" PULLMODE=NONE IO_TYPE=LVCMOS33;
FREQUENCY PORT "CLK" 150 MHZ;
SYSCONFIG CONFIG_IOVOLTAGE=3.3 COMPRESS_CONFIG=ON MCCLK_FREQ=62 SLAVE_SPI_PORT=DISABLE MASTER_SPI_PORT=ENABLE SLAVE_PARALLEL_PORT=DISABLE;
LOCATE COMP "tx_out" SITE "L4"; # FPGA transmits to ftdi
LOCATE COMP "rx_in" SITE "M1"; # FPGA receives from ftdi
IOBUF PORT "tx_out" PULLMODE=UP IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "rx_in" PULLMODE=UP IO_TYPE=LVCMOS33;
LOCATE COMP "leds[7]" SITE "H3";
LOCATE COMP "leds[6]" SITE "E1";
LOCATE COMP "leds[5]" SITE "E2";
LOCATE COMP "leds[4]" SITE "D1";
LOCATE COMP "leds[3]" SITE "D2";
LOCATE COMP "leds[2]" SITE "C1";
LOCATE COMP "leds[1]" SITE "C2";
LOCATE COMP "leds[0]" SITE "B2";
IOBUF PORT "leds[0]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "leds[1]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "leds[2]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "leds[3]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "leds[4]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "leds[5]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "leds[6]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
IOBUF PORT "leds[7]" PULLMODE=NONE IO_TYPE=LVCMOS33 DRIVE=4;
LOCATE COMP "RST_N" SITE "B3";
IOBUF PORT "RST_N" PULLMODE=DOWN IO_TYPE=LVCMOS33;

View File

@ -24,7 +24,7 @@
gotools gotools
gtkwave gtkwave
imagemagick imagemagick
nextpnr nextpnrWithGui
openfpgaloader openfpgaloader
picocom picocom
(python3.withPackages (py-pkgs: [ (python3.withPackages (py-pkgs: [

30
lib/GlitchFilter.bsv Normal file
View File

@ -0,0 +1,30 @@
package GlitchFilter;
import Cntrs::*;
(* always_enabled="_write",always_ready="_read" *)
module mkGlitchFilter(Integer hysteresis, bit init_value, Reg#(bit) ifc);
Wire#(bit) in <- mkBypassWire();
UCount cnt <- mkUCount(init_value == 0 ? 0 : hysteresis, hysteresis);
Reg#(bit) out[2] <- mkCReg(2, init_value);
(* no_implicit_conditions, fire_when_enabled *)
rule incr (cnt.isLessThan(fromInteger(hysteresis)) && in == 1);
cnt.incr(1);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule decr (cnt.isGreaterThan(0) && in == 0);
cnt.decr(1);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule set_out (cnt.isEqual(0) || cnt.isEqual(fromInteger(hysteresis)));
out[0] <= cnt.isEqual(0) ? 0 : 1;
endrule
method _write = in._write;
method _read = out[1]._read;
endmodule
endpackage

76
lib/GlitchFilter_Test.bsv Normal file
View File

@ -0,0 +1,76 @@
package GlitchFilter_Test;
import Assert::*;
import StmtFSM::*;
import GlitchFilter::*;
import Testing::*;
module mkTB();
let testflags <- mkTestFlags();
let cycles <- mkCycleCounter();
let dut <- mkGlitchFilter(3, 0);
// Slight indirection to appease the compiler: GlitchFilter
// requires a write on every cycle, and with just the test FSM, the
// compiler can't prove that this is satisfied. So, give it a
// default value when the test isn't driving the input, on the
// first few cycles of the test.
Wire#(bit) pin_in <- mkDWire(0);
(* no_implicit_conditions,fire_when_enabled *)
rule push_in;
dut <= pin_in;
endrule
function Action check_dut(bit in, bit want_out);
return action
if (testflags.verbose)
$display("%0d: GlitchFilter(%0d) => %0d, want %0d", cycles.all, in, dut, want_out);
dynamicAssert(dut == want_out, "wrong GlitchFilter output");
pin_in <= in;
endaction;
endfunction
runTest(100,
mkTest("GlitchFilter", seq
// Simple 0->1 transition
check_dut(0, 0);
check_dut(1, 0);
check_dut(1, 0);
check_dut(1, 0);
check_dut(1, 1);
check_dut(1, 1);
// Simple 1->0 transition
check_dut(0, 1);
check_dut(0, 1);
check_dut(0, 1);
check_dut(0, 0);
check_dut(0, 0);
// Glitchy 0->1
check_dut(1, 0);
check_dut(1, 0);
check_dut(0, 0);
check_dut(1, 0);
check_dut(0, 0);
check_dut(1, 0);
check_dut(1, 0);
check_dut(1, 1);
check_dut(1, 1);
// Glitchy 1->0
check_dut(0, 1);
check_dut(0, 1);
check_dut(1, 1);
check_dut(0, 1);
check_dut(1, 1);
check_dut(0, 1);
check_dut(0, 1);
check_dut(0, 0);
check_dut(0, 0);
endseq));
endmodule
endpackage

View File

@ -1,11 +1,5 @@
package PinSync; package PinSync;
(* always_ready, always_enabled *)
interface PinSync#(type val);
method Action _write(val a);
method val _read();
endinterface
// mkPinSync builds a synchronizer for use with asynchronous inputs. // mkPinSync builds a synchronizer for use with asynchronous inputs.
// //
// You should only use this to capture asynchronous inputs coming from // You should only use this to capture asynchronous inputs coming from
@ -24,7 +18,8 @@ endinterface
// source domain. Conceptually, we assume that register exists outside // source domain. Conceptually, we assume that register exists outside
// our design and is driving the input of mkPinSync, so we just need // our design and is driving the input of mkPinSync, so we just need
// the metastability mitigation within our own domain. // the metastability mitigation within our own domain.
module mkPinSync(val init_value, PinSync#(val) ifc) (* always_enabled="_write",always_ready="_read" *)
module mkPinSync(val init_value, Reg#(val) ifc)
provisos(Bits#(val, _)); provisos(Bits#(val, _));
Reg#(val) r1 <- mkReg(init_value); Reg#(val) r1 <- mkReg(init_value);

View File

@ -10,7 +10,7 @@ module mkTB();
let testflags <- mkTestFlags(); let testflags <- mkTestFlags();
let cycles <- mkCycleCounter(); let cycles <- mkCycleCounter();
PinSync#(UInt#(2)) dut <- mkPinSync(0); Reg#(UInt#(2)) dut <- mkPinSync(0);
function Action check_dut_val(UInt#(2) want_val); function Action check_dut_val(UInt#(2) want_val);
return action return action

View File

@ -2,6 +2,7 @@ package Strobe;
import Real::*; import Real::*;
import Printf::*; import Printf::*;
import DReg::*;
// A Strobe provides a synchronization signal to other modules, when // A Strobe provides a synchronization signal to other modules, when
// an event happens at a cadence other than the module clock. // an event happens at a cadence other than the module clock.
@ -9,49 +10,134 @@ import Printf::*;
interface Strobe; interface Strobe;
method Bool _read(); method Bool _read();
// reset resets the strobe cycle, starting with a strobe on the // reset resets the strobe cycle, starting with a strobe on the
// cycle following reset. // cycle following the reset.
method Action reset(); method Action reset();
endinterface endinterface
module mkStrobeRaw(UInt#(sz) increment, UInt#(sz) max, Strobe ifc);
Reg#(UInt#(sz)) cnt <- mkReg(0);
PulseWire want_reset <- mkPulseWire();
Reg#(Bool) strobe_out <- mkDReg(False);
(* no_implicit_conditions, fire_when_enabled *)
rule increment;
if (want_reset) begin
cnt <= 0;
strobe_out <= True;
end
else begin
UInt#(TAdd#(sz, 1)) new_cnt = extend(cnt)+extend(increment);
if (new_cnt >= extend(max)) begin
new_cnt = new_cnt - extend(max);
strobe_out <= True;
end
cnt <= truncate(new_cnt);
end
endrule
method Bool _read = strobe_out._read;
method Action reset();
want_reset.send();
endmethod
endmodule
module mkStrobeRawOld(UInt#(sz) increment, UInt#(sz) max, Strobe ifc);
Reg#(UInt#(sz)) cnt[2] <- mkCReg(2, 0);
Reg#(Bool) pulse <- mkDReg(False);
PulseWire pw <- mkPulseWireOR();
(* no_implicit_conditions, fire_when_enabled *)
rule increment;
UInt#(TAdd#(sz, 1)) new_cnt = extend(cnt[0])+extend(increment);
if (new_cnt >= extend(max)) begin
cnt[0] <= truncate(new_cnt - extend(max));
pulse <= True;
end
else
cnt[0] <= truncate(new_cnt);
endrule
(* no_implicit_conditions, fire_when_enabled *)
rule strobe (pulse);
pw.send();
endrule
method _read = pw._read;
method Action reset();
cnt[1] <= increment;
pw.send();
endmethod
endmodule
module mkStrobeRawRec(UInt#(n) hack, Integer increment, Integer max, Strobe ifc);
let register_width = ceil(log2(fromInteger(max)));
if (valueOf(n) < register_width) begin
UInt#(TAdd#(n, 1)) hack2 = 0;
let _ret <- mkStrobeRawRec(hack2, increment, max);
return _ret;
end
else begin
UInt#(n) hack2 = fromInteger(increment);
let _ret <- mkStrobeRaw(hack2, fromInteger(max));
return _ret;
end
endmodule
// mkStrobe returns a Strobe that triggers at the given // mkStrobe returns a Strobe that triggers at the given
// target_frequency, assuming mkStrobe is being clocked at the given // target_frequency, assuming mkStrobe is being clocked at the given
// higher clock_frequency. // higher clock_frequency.
module mkStrobe(Integer clock_frequency, Integer target_frequency, Strobe ifc); module mkStrobe(Integer clock_frequency, Integer target_frequency, Strobe ifc);
if (target_frequency > clock_frequency) if (target_frequency > clock_frequency)
error("mkStrobe target_frequency must be less than clock_frequency"); error("mkStrobe target_frequency must be less than clock_frequency");
let strobe_every = round(fromInteger(clock_frequency)/fromInteger(target_frequency));
// Because we're using integer counters to divide frequencies, // This is how many main clock ticks there are per strobe at the
// unless the clock and target frequencies divide cleanly we'll end // target frequency. For all but the simplest cases, this will
// up with a small amount of error. // include fractional clock cycles due to the two frequencies not
// dividing evenly.
Real target_ratio = fromInteger(clock_frequency)/fromInteger(target_frequency);
// At this point we could round the target ratio and instantiate a
// counter for that amount, but the resulting strobe frequency can
// be quite drastically off target (empirically I've seen up to 5%
// without trying).
// //
// Strobes like this tend to be used for relatively short // We can improve on this by counting in larger increments modulo
// operations before some other synchronization event happens // some maximum, and strobing on every overflow. This will make the
// (e.g. sending one byte on UART), so we can allow a small amount // strobe period vary slightly by the main clock's reckoning,
// of frequency error. For now, the target frequency error is fixed // jittering around the "true" strobe time as error accumulates and
// at <=0.1%. // is then subtracted back out.
Real actual_frequency = fromInteger(clock_frequency)/fromInteger(strobe_every); Real target_error = 1/1000; // 0.1%
Real frequency_error_pct = abs(fromInteger(target_frequency)-actual_frequency) / fromInteger(target_frequency) * 100;
if (frequency_error_pct > 0.1)
error(sprintf("mkStrobe actual frequency is %0f, %0f%% error vs. requested %0d. Your clock_frequency and target_frequency are probably too near each other.", actual_frequency, frequency_error_pct, target_frequency));
Reg#(UInt#(32)) cnt[2] <- mkCReg(2, 0); Integer incr;
Integer max;
Real actual_error = 1;
for (incr=1; incr<=32 && actual_error > target_error; incr=incr+1) begin
Real mul_ratio = fromInteger(incr)*target_ratio;
max = round(mul_ratio);
actual_error = abs(mul_ratio - fromInteger(max))/fromInteger(max);
end
(* no_implicit_conditions, fire_when_enabled *) // Exited the loop, so either we found a good counter width to hit
rule increment; // the requested error, or we maxed out on a 64-bit counter.
if (cnt[0] == fromInteger(strobe_every-1)) if (actual_error > target_error)
cnt[0] <= 0; error(sprintf("cannot construct a strobe running at %0dHz from a main clock of %0dHz without exceeding the target frequency error of %0d%%", target_frequency, clock_frequency, target_error*100));
else
cnt[0] <= cnt[0]+1;
endrule
method Bool _read(); // The loop over-incremented by 1 after finding an acceptable error amount.
return cnt[0] == 0; incr = incr-1;
endmethod
method Action reset(); // Debug messages, uncomment if you're wondering what the logic
cnt[1] <= 0; // above decided for your main clock and target strobe rate.
endmethod Bool show_debug = True;
if (show_debug) begin
Real actual_ratio = fromInteger(max)/fromInteger(incr);
Real actual_freq_low = fromInteger(clock_frequency)/fromInteger(ceil(actual_ratio));
Real actual_freq_high = fromInteger(clock_frequency)/fromInteger(floor(actual_ratio));
Real actual_freq_avg = fromInteger(clock_frequency)/actual_ratio;
messageM(sprintf("Strobe at %0dHz given clock at %0dHz:\n count by %0d mod %0d\n strobing every %d to %d cycles\n effective strobe frequency %0.2fHz to %0.2fHz\n avg frequency %0.2fHz (%0.2f%% error)", target_frequency, clock_frequency, incr, max, floor(actual_ratio), ceil(actual_ratio), actual_freq_low, actual_freq_high, actual_freq_avg, actual_error*100));
end
let _ret <- mkStrobeRawRec(UInt#(1) ' (0), incr, max);
return _ret;
endmodule endmodule
endpackage endpackage

View File

@ -11,10 +11,12 @@ module mkTB();
let cycles <- mkCycleCounter(); let cycles <- mkCycleCounter();
// For this test, we assume we're clocked at 25MHz, and want a // For this test, we assume we're clocked at 25MHz, and want a
// 115_200bps strobe for a serial port. That translates to a strobe // 16x115_200bps strobe for a serial port. That translates to a
// every 217 cycles. // strobe that happens every 13 or 14 cycles, depending on the
let dut <- mkStrobe(25_000_000, 115_200); // amount of accumulated error from the imprecise frequency
let want_pulse_every = 217; // division.
let dut <- mkStrobe(25_000_000, 115_200*16);
let want_pulse_every = 14;
function Action check_dut(Bool want); function Action check_dut(Bool want);
return action return action
@ -24,29 +26,39 @@ module mkTB();
endaction; endaction;
endfunction endfunction
function Stmt check_one_cycle(); function Stmt check_one_cycle(Integer cycle_len);
return seq return seq
action action
$display("%0d: cycle start", cycles.all);
check_dut(True); check_dut(True);
cycles.reset(); cycles.reset();
endaction endaction
while (cycles < want_pulse_every) while (cycles < fromInteger(cycle_len))
check_dut(False); check_dut(False);
endseq; endseq;
endfunction endfunction
runTest(2000, runTest(500,
mkTest("Strobe", seq mkTest("Strobe", seq
dut.reset(); dut.reset();
repeat(3) check_one_cycle(); check_one_cycle(14);
check_one_cycle(14);
check_one_cycle(13);
check_one_cycle(14);
check_one_cycle(13);
check_one_cycle(14);
check_one_cycle(13);
check_one_cycle(14);
check_one_cycle(14);
check_one_cycle(13);
check_one_cycle(14);
check_one_cycle(13);
// Reset should actually reset // Reset should actually reset
repeat(10) noAction; repeat(10) noAction;
par
check_dut(False);
dut.reset(); dut.reset();
endpar check_one_cycle(14);
repeat(3) check_one_cycle(); check_one_cycle(14);
endseq)); endseq));
endmodule endmodule

View File

@ -137,7 +137,7 @@ def build(c, target):
return verilog_files return verilog_files
@task @task
def synth(c, target): def synth(c, target, gui=False):
target = resolve_synth_target(target) target = resolve_synth_target(target)
out_info, out_verilog, out_bsc, out_yosys, out_nextpnr = ensure_build_dirs(target, "info", "verilog", "bsc", "yosys", "nextpnr") out_info, out_verilog, out_bsc, out_yosys, out_nextpnr = ensure_build_dirs(target, "info", "verilog", "bsc", "yosys", "nextpnr")
@ -192,11 +192,16 @@ def synth(c, target):
pin_map_arg = f"--lpf {pin_map}" pin_map_arg = f"--lpf {pin_map}"
else: else:
print(f"WARNING: no pin map at {pin_map}, executing place&route with no constraints") print(f"WARNING: no pin map at {pin_map}, executing place&route with no constraints")
pin_map_arg = f"--lpf-allow-unconstrained --lpf=lib/ulx3s_v20.lpf" pin_map_arg = f"--lpf-allow-unconstrained --lpf=lib/ulx3s_v20.lpf --freq 100"
nextpnr_out_json = out_nextpnr / f"{module_name.stem}_routed.json"
nextpnr_out = out_nextpnr / module_name.with_suffix(".pnr") nextpnr_out = out_nextpnr / module_name.with_suffix(".pnr")
nextpnr_log = out_nextpnr / module_name.with_suffix(".log") nextpnr_log = out_nextpnr / module_name.with_suffix(".log")
nextpnr_timing = out_nextpnr / module_name.with_suffix(".timing.log") nextpnr_timing = out_nextpnr / module_name.with_suffix(".timing.log")
out = c.run(f"nextpnr-ecp5 --85k --detailed-timing-report -l {nextpnr_log} --report {nextpnr_timing} --json {yosys_json} --package=CABGA381 {pin_map_arg} --speed=6 --textcfg {nextpnr_out}", hide='stderr') if gui:
cmd = f"nextpnr-ecp5 --85k --detailed-timing-report --report {nextpnr_timing} --json {yosys_json} --gui --gui-no-aa --package=CABGA381 {pin_map_arg} --speed=6 --textcfg {nextpnr_out}"
else:
cmd = f"nextpnr-ecp5 --85k --detailed-timing-report -l {nextpnr_log} --report {nextpnr_timing} --json {yosys_json} --write {nextpnr_out_json} --package=CABGA381 {pin_map_arg} --speed=6 --textcfg {nextpnr_out}"
out = c.run(cmd)
print_filtered_paragraphs(out.stderr, "Device utilisation", "Critical path", "Max frequency", "Max delay", common_prefix="Info: ") print_filtered_paragraphs(out.stderr, "Device utilisation", "Critical path", "Max frequency", "Max delay", common_prefix="Info: ")
print(f" PNR : {nextpnr_out}") print(f" PNR : {nextpnr_out}")