diff --git a/BUILD.conf b/BUILD.conf index f5f78fd2..68fc6f8b 100644 --- a/BUILD.conf +++ b/BUILD.conf @@ -117,16 +117,23 @@ environment('icestick', base = 'ice40', contents = { ], 'nextpnr_constraints': ROOT + '/hdl/projects/icestick/icestick.pcf', }) -environment('ignition_target', base = 'ice40', contents = { +environment('ignition_target', base = 'default', contents = { 'bsc_flags': [ '-opt-undetermined-vals', '-unspecified-to', 'X', ], + 'yosys_cmds': [ + 'synth_ice40 -top $$top_module -dffe_min_ce_use 4', + ], + 'yosys_backend': 'json', # nextpnr assumes JSON input. + 'nextpnr_ice40': VARS.get('nextpnr', 'ice40', default='nextpnr-ice40'), 'nextpnr_ice40_flags': [ + '-q', '--lp1k', '--package qn84', '--freq 50', ], + 'nextpnr_ice40_pack': VARS.get('nextpnr', 'ice40_pack', default='icepack'), 'nextpnr_constraints': ROOT + '/hdl/ip/bsv/ignition/ignition_target.pcf', }) environment('gimlet', base = 'ice40', contents = { @@ -136,13 +143,26 @@ environment('gimlet', base = 'ice40', contents = { '--freq 50', ], }) -environment('sidecar_mainboard_controller', base = 'ecp5', contents = { +environment('sidecar_mainboard_controller', base = 'default', contents = { + # The -no-rw-check needs to be added to the synth_ecp5 command to properly + # infer RAMs, allowing the Ignition Controller design to synthesize. + 'yosys_cmds': [ + 'synth_ecp5 -top $$top_module -no-rw-check', + ], + 'yosys_backend': 'json', + 'bsc_flags': [ + '-opt-undetermined-vals', + '-unspecified-to', 'X', + ], + 'nextpnr_ecp5': VARS.get('nextpnr', 'ecp5', default='nextpnr-ecp5'), 'nextpnr_ecp5_flags': [ + '-q', '--45k', '--package CABGA554', '--speed 6', '--freq 50', ], + 'nextpnr_ecp5_pack': VARS.get('nextpnr', 'ecp5_pack', default='ecppack'), 'nextpnr_ecp5_pack_flags': [ '--compress', ], diff --git a/hdl/ip/bsv/BUILD b/hdl/ip/bsv/BUILD index fade9c94..7d84b607 100644 --- a/hdl/ip/bsv/BUILD +++ b/hdl/ip/bsv/BUILD @@ -67,6 +67,11 @@ bluespec_library('Countdown', 'Countdown.bsv', ]) +bluespec_library('CounterRAM', + sources = [ + 'CounterRAM.bsv', + ]) + bluespec_library('Debouncer', sources = [ 'Debouncer.bsv', diff --git a/hdl/ip/bsv/CounterRAM.bsv b/hdl/ip/bsv/CounterRAM.bsv new file mode 100644 index 00000000..fe0565dd --- /dev/null +++ b/hdl/ip/bsv/CounterRAM.bsv @@ -0,0 +1,264 @@ +package CounterRAM; + +import BRAMCore::*; +import ClientServer::*; +import FIFO::*; +import FIFOF::*; +import GetPut::*; +import Probe::*; + +import DReg::*; + + +typedef UInt#(counter_sz) Value#(type counter_sz); +typedef UInt#(amount_sz) Amount#(type amount_sz); + +typedef struct { + Bool clear; + counter_id id; +} CounterReadRequest#(type counter_id) deriving (Bits, FShow); + +typedef enum { + Set, + Add, + Subtract +} ProducerOp deriving (Bits, Eq, FShow); + +typedef struct { + ProducerOp op; + Amount#(amount_sz) amount; + counter_id id; +} CounterWriteRequest#(type counter_id, numeric type amount_sz) + deriving (Bits, FShow); + +typedef struct { + Value#(counter_sz) value; + counter_id id; +} CounterWrite#(type counter_id, numeric type counter_sz) + deriving (Bits, FShow); + +interface Producer #(type counter_id, numeric type amount_sz); + interface Put#(CounterWriteRequest#(counter_id, amount_sz)) request; + method Bool idle(); +endinterface + +typedef Server#(CounterReadRequest#(counter_id), Value#(counter_sz)) + Consumer#(type counter_id, numeric type counter_sz); + +interface CounterRAM #( + type counter_id, + numeric type counter_sz, + numeric type amount_sz); + interface Producer#(counter_id, amount_sz) producer; + interface Consumer#(counter_id, counter_sz) consumer; +endinterface + +module mkCounterRAM #(Integer n) + (CounterRAM#(counter_id, counter_sz, amount_sz)) + provisos ( + Bits#(counter_id, counter_id_sz), + Eq#(counter_id), + FShow#(counter_id), + // Make sure Amount fits in Value. + Add#(amount_sz, _, counter_sz)); + BRAM_DUAL_PORT#(counter_id, Value#(counter_sz)) + ram <- mkBRAMCore2(n, False); + + let producer_port = ram.a; + let consumer_port = ram.b; + + Reg#(ProducerState#(counter_id, counter_sz, amount_sz)) + producer_state <- mkReg(tagged AwaitingRequest); + RWire#(CounterWriteRequest#(counter_id, amount_sz)) + producer_request_next <- mkRWire(); + + Reg#(ConsumerState#(counter_id)) + consumer_state <- mkReg(tagged AwaitingRequest); + RWire#(CounterReadRequest#(counter_id)) consumer_request_next <- mkRWire(); + + // Use a FIFO with unguarded enq, allowing it to be used without blocking + // unrelated parts of the `do_handle_requests` rule. The rule explicitly + // checks `notFull` to avoid overwriting responses. + FIFOF#(Value#(counter_sz)) consumer_response <- mkGLFIFOF(True, False); + + let producer_idle = + case (producer_state) matches + tagged AwaitingRequest: True; + tagged AwaitingWriteCommit .*: True; + default: False; + endcase; + + let consumer_idle = + case (consumer_state) matches + tagged RequestPending .*: False; + tagged ReadingValue .request: !request.clear; + default: True; + endcase; + + // + // Counter producer and consumer requests can be issued concurrently but + // because a read from and a write to the same RAM address may result in an + // undefined value being read there needs to be some collision detection + // logic in place to handle these cases. This logic depends on data from + // both requests and implementing all of it in a single rule is most + // straightforward. + // + // Both counter producer and consumer requests follow a similar sequence. + // Upon accepting a request the current value of the counter is read from + // RAM. A consumer request may then clear the counter value by writing a 0 + // to RAM while a counter write request will update the counter value and + // write it back to RAM. + // + (* fire_when_enabled *) + rule do_handle_requests; + let producer_performs_clear = + case (tuple2(producer_state, consumer_state)) matches + {tagged ReadingValue .write, + tagged RequestPending .read}: + (write.id == read.id && read.clear); + {tagged ReadingValue .write, + tagged ReadingValue .read}: + (write.id == read.id && read.clear); + {tagged ReadingValue .write, + tagged AwaitingWriteCommit .clear_id}: + (write.id == clear_id); + {tagged WritePending .write, + tagged ReadingValue .read}: + (write.id == read.id && read.clear); + default: False; + endcase; + + if (consumer_state matches tagged AwaitingWriteCommit .*) + if (consumer_request_next.wget matches tagged Valid .next_request) + consumer_state <= tagged RequestPending next_request; + else + consumer_state <= tagged AwaitingRequest; + + // Select the counter value returned to the consumer, reading from the + // producer or the consumer port depending on whether or not there is a + // update/clear conflict. + else if (consumer_state matches + tagged ReadingValue .request) begin + let counter_value = + case (producer_state) matches + // If the producer is updating the same counter, use the + // value of that write request. + tagged WritePending .write_request &&& + (request.id == write_request.id): + write_request.value; + tagged AwaitingWriteCommit .write_request &&& + (request.id == write_request.id): + write_request.value; + + // In all other cases read the counter value from the + // consumer port. + default: consumer_port.read; + endcase; + + consumer_response.enq(counter_value); + + if (request.clear && !producer_performs_clear) begin + consumer_port.put(True, request.id, 0); + consumer_state <= tagged AwaitingWriteCommit request.id; + end + else if (consumer_request_next.wget matches + tagged Valid .next_request) + consumer_state <= tagged RequestPending next_request; + else + consumer_state <= tagged AwaitingRequest; + end + // Start the pending consumer request if the response can be enqueued. + else if (consumer_state matches tagged RequestPending .read &&& + consumer_response.notFull) begin + consumer_port.put(False, read.id, ?); + consumer_state <= tagged ReadingValue read; + end + // Accept the next consumer request. + else if (consumer_state matches tagged AwaitingRequest &&& + consumer_request_next.wget matches tagged Valid .request) + consumer_state <= tagged RequestPending request; + + if (producer_state matches tagged AwaitingWriteCommit .*) begin + if (producer_request_next.wget matches tagged Valid .request) + producer_state <= tagged RequestPending request; + else + producer_state <= tagged AwaitingRequest; + end + + else if (producer_state matches tagged WritePending .write) begin + producer_port.put(True, write.id, write.value); + producer_state <= tagged AwaitingWriteCommit write; + end + + else if (producer_state matches tagged ReadingValue .request) begin + let value = producer_performs_clear ? 0 : producer_port.read; + let amount = extend(request.amount); + let write = + CounterWrite { + id: request.id, + value: + case (request.op) + Set: amount; + Add: boundedPlus(value, amount); + Subtract: boundedMinus(value, amount); + endcase}; + + producer_state <= tagged WritePending write; + end + + else if (producer_state matches tagged RequestPending .request) begin + producer_port.put(False, request.id, ?); + producer_state <= tagged ReadingValue request; + end + + else if (producer_state matches tagged AwaitingRequest &&& + producer_request_next.wget matches tagged Valid .request) + producer_state <= tagged RequestPending request; + + // $display( + // fshow(producer_state), + // " ", + // fshow(consumer_state), + // " ", + // fshow(producer_performs_clear)); + endrule + + interface Producer producer; + interface Put request; + method put if (producer_idle) = + producer_request_next.wset; + endinterface + + method idle = producer_idle; + endinterface + + interface Server consumer; + interface Put request; + method put if (consumer_idle) = + consumer_request_next.wset; + endinterface + + interface Get response = toGet(fifofToFifo(consumer_response)); + endinterface +endmodule + +typedef union tagged { + void AwaitingRequest; + CounterWriteRequest#(counter_id, amount_sz) RequestPending; + CounterWriteRequest#(counter_id, amount_sz) ReadingValue; + CounterWrite#(counter_id, counter_sz) WritePending; + CounterWrite#(counter_id, counter_sz) AwaitingWriteCommit; +} ProducerState#( + type counter_id, + numeric type counter_sz, + numeric type amount_sz) + deriving (Bits, FShow); + +typedef union tagged { + void AwaitingRequest; + CounterReadRequest#(counter_id) RequestPending; + CounterReadRequest#(counter_id) ReadingValue; + counter_id AwaitingWriteCommit; +} ConsumerState#(type counter_id) deriving (Bits, FShow); + +endpackage diff --git a/hdl/ip/bsv/Deserializer8b10b.bsv b/hdl/ip/bsv/Deserializer8b10b.bsv index 940a1b8f..b545b999 100644 --- a/hdl/ip/bsv/Deserializer8b10b.bsv +++ b/hdl/ip/bsv/Deserializer8b10b.bsv @@ -8,8 +8,10 @@ package Deserializer8b10b; export DeserializedCharacter(..); export Deserializer(..); +export DeserializerClient(..); export mkDeserializer; +import Connectable::*; import GetPut::*; import FIFO::*; import FIFOF::*; @@ -39,6 +41,12 @@ interface Deserializer; method Action invert_polarity(); endinterface +interface DeserializerClient; + interface PutS#(DeserializedCharacter) character; + method Bool search_for_comma(); + method Bool invert_polarity(); +endinterface + // A minimal implementation of the `Deserializer` interface. This module expects // downstream logic to dequeue received characters before the next character has // been completely received. Failure to do so will mean the previous character @@ -101,4 +109,20 @@ module mkDeserializer (Deserializer); method invert_polarity = invert_polarity_.send; endmodule +instance Connectable#(Deserializer, DeserializerClient); + module mkConnection #(Deserializer des, DeserializerClient client) (Empty); + mkConnection(des.character, client.character); + + (* fire_when_enabled *) + rule do_search_for_comma (client.search_for_comma); + des.search_for_comma(); + endrule + + (* fire_when_enabled *) + rule do_invert_polarity (client.invert_polarity); + des.invert_polarity(); + endrule + endmodule +endinstance + endpackage diff --git a/hdl/ip/bsv/TestUtils.bsv b/hdl/ip/bsv/TestUtils.bsv index 5ec49106..375127fb 100644 --- a/hdl/ip/bsv/TestUtils.bsv +++ b/hdl/ip/bsv/TestUtils.bsv @@ -12,33 +12,55 @@ export assert_not_set; export assert_true; export assert_false; export assert_eq; +export assert_eq_fmt; +export assert_eq_display; +export assert_eq_display_fmt; export assert_not_eq; +export assert_not_eq_fmt; +export assert_not_eq_display; +export assert_not_eq_display_fmt; +export assert_bits_eq; +export assert_bits_eq_display; +export assert_bits_not_eq; +export assert_bits_not_eq_display; export assert_av_set; export assert_av_not_set; export assert_av_true; export assert_av_false; export assert_av_eq; -export assert_av_not_eq; -export assert_av_any; export assert_av_eq_display; -export assert_av_not_eq_display; -export assert_av_any_display; export assert_av_eq_display_fmt; +export assert_av_not_eq; +export assert_av_not_eq_display; export assert_av_not_eq_display_fmt; +export assert_av_any; +export assert_av_any_display; export assert_av_any_display_fmt; +export assert_av_bits_eq; +export assert_av_bits_eq_display; +export assert_av_bits_not_eq; +export assert_av_bits_not_eq_display; +export assert_av_bits_any; +export assert_av_bits_any_display; export assert_get_set; export assert_get_not_set; export assert_get_true; export assert_get_false; export assert_get_eq; -export assert_get_not_eq; -export assert_get_any; export assert_get_eq_display; -export assert_get_not_eq_display; -export assert_get_any_display; export assert_get_eq_display_fmt; +export assert_get_not_eq; +export assert_get_not_eq_display; export assert_get_not_eq_display_fmt; +export assert_get_any; +export assert_get_any_display; export assert_get_any_display_fmt; +export assert_get_bits_eq; +export assert_get_bits_eq_display; +export assert_get_bits_not_eq; +export assert_get_bits_not_eq_display; +export assert_get_bits_any; +export assert_get_bits_any_display; export TestResult(..); export Test(..); @@ -105,14 +127,106 @@ function Action assert_not_eq(t actual, t expected, String msg) provisos ( Eq#(t), FShow#(t)); + return action + if (actual == expected) $display("value: ", fshow(actual)); + dynamicAssert(actual != expected, msg); + endaction; +endfunction + +function Action assert_eq_fmt( + t actual, + t expected, + String msg, + function Fmt fmt(t v)) + provisos (Eq#(t)); + return action + if (actual != expected) begin + $display("expected: ", fmt(expected)); + $display("actual: ", fmt(actual)); + end + dynamicAssert(actual == expected, msg); + endaction; +endfunction + +function Action assert_not_eq_fmt( + t actual, + t expected, + String msg, + function Fmt fmt(t v)) + provisos (Eq#(t)); return action if (actual == expected) begin - $display("value: ", fshow(actual)); + $display("expected: ", fmt(expected)); + $display("actual: ", fmt(actual)); end dynamicAssert(actual != expected, msg); endaction; endfunction +function Action assert_bits_eq(t actual, t expected, String msg) + provisos (Bits#(t, t_sz)); + return assert_eq(pack(actual), pack(expected), msg); +endfunction + +function Action assert_bits_not_eq(t actual, t expected, String msg) + provisos (Bits#(t, t_sz)); + return assert_not_eq(pack(actual), pack(expected), msg); +endfunction + +function Action assert_eq_display(t actual, t expected, String msg) + provisos ( + Eq#(t), + FShow#(t)); + return action + if (actual == expected) $display(fshow(actual)); + assert_eq(actual, expected, msg); + endaction; +endfunction + +function Action assert_not_eq_display(t actual, t expected, String msg) + provisos ( + Eq#(t), + FShow#(t)); + return action + if (actual == expected) $display(fshow(actual)); + assert_eq(actual, expected, msg); + endaction; +endfunction + +function Action assert_eq_display_fmt( + t actual, + t expected, + String msg, + function Fmt fmt(t v)) + provisos (Eq#(t)); + return action + if (actual == expected) $display(fmt(actual)); + assert_eq_fmt(actual, expected, msg, fmt); + endaction; +endfunction + +function Action assert_not_eq_display_fmt( + t actual, + t expected, + String msg, + function Fmt fmt(t v)) + provisos (Eq#(t)); + return action + if (actual != expected) $display(fmt(actual)); + assert_not_eq_fmt(actual, expected, msg, fmt); + endaction; +endfunction + +function Action assert_bits_eq_display(t actual, t expected, String msg) + provisos (Bits#(t, t_sz)); + return assert_eq_display(pack(actual), pack(expected), msg); +endfunction + +function Action assert_bits_not_eq_display(t actual, t expected, String msg) + provisos (Bits#(t, t_sz)); + return assert_not_eq_display(pack(actual), pack(expected), msg); +endfunction + // // `assert_av_set(..)`/`assert_av_not_set(..)` assert that the result of the // given `ActionValue` is set or not set respectively. @@ -171,6 +285,24 @@ function Action assert_av_not_eq(ActionValue#(t) av, t expected, String msg) endaction; endfunction +function Action assert_av_bits_eq(ActionValue#(t) av, t expected, String msg) + provisos ( + Bits#(t, t_sz)); + return action + let actual <- av; + assert_eq(pack(actual), pack(expected), msg); + endaction; +endfunction + +function Action assert_av_bits_not_eq(ActionValue#(t) av, t expected, String msg) + provisos ( + Bits#(t, t_sz)); + return action + let actual <- av; + assert_not_eq(pack(actual), pack(expected), msg); + endaction; +endfunction + // // `assert_av_any(..)` collects any value from the given `ActionValue`. It does // not assert anything about this action, but provides some syntactic sugar when @@ -182,6 +314,10 @@ function Action assert_av_any(ActionValue#(t) av); endaction; endfunction +function Action assert_av_bits_any(ActionValue#(t) av); + return assert_av_any(av); +endfunction + // // `assert_av_eq_display(..)`/`assert_av_not_eq_display(..) assert that the // result of the given `ActionValue` does or does not match an expected @@ -214,6 +350,36 @@ function Action assert_av_not_eq_display( endaction; endfunction +function Action assert_av_bits_eq_display( + ActionValue#(t) av, + t expected, + String msg) + provisos (Bits#(t, t_sz)); + return action + let actual <- av; + let actual_ = pack(actual); + let expected_ = pack(expected); + + if (actual_ == expected_) $display(fshow(actual_)); + assert_eq(actual_, expected_, msg); + endaction; +endfunction + +function Action assert_av_bits_not_eq_display( + ActionValue#(t) av, + t expected, + String msg) + provisos (Bits#(t, t_sz)); + return action + let actual <- av; + let actual_ = pack(actual); + let expected_ = pack(expected); + + if (actual_ != expected_) $display(fshow(actual_)); + assert_not_eq(actual_, expected_, msg); + endaction; +endfunction + // // `assert_av_any_display(..)` collects and displays any value from the given // `ActionValue`. It does not assert anything about this action, but provides @@ -229,6 +395,14 @@ function Action assert_av_any_display(ActionValue#(t) av) endaction; endfunction +function Action assert_av_bits_any_display(ActionValue#(t) av) + provisos (Bits#(t, t_sz)); + return action + let v <- av; + $display(fshow(pack(v))); + endaction; +endfunction + // // `assert_av_eq_display_fmt(..)`/`assert_av_not_eq_display_fmt(..) assert that // the result of the given `ActionValue` does or does not match an expected @@ -245,13 +419,7 @@ function Action assert_av_eq_display_fmt( provisos (Eq#(t)); return action let actual <- av; - if (actual == expected) - $display(fmt(actual)); - else begin - $display("expected: ", fmt(expected)); - $display("actual: ", fmt(actual)); - end - assert_true(actual == expected, msg); + assert_eq_display_fmt(actual, expected, msg, fmt); endaction; endfunction @@ -263,12 +431,7 @@ function Action assert_av_not_eq_display_fmt( provisos (Eq#(t)); return action let actual <- av; - if (actual != expected) - $display(fmt(actual)); - else begin - $display("value: ", fmt(actual)); - end - assert_true(actual != expected, msg); + assert_not_eq_display_fmt(actual, expected, msg, fmt); endaction; endfunction @@ -335,6 +498,16 @@ function Action assert_get_not_eq(Get#(t) g, t expected, String msg) return assert_av_not_eq(g.get, expected, msg); endfunction +function Action assert_get_bits_eq(Get#(t) g, t expected, String msg) + provisos (Bits#(t, t_sz)); + return assert_av_bits_eq(g.get, expected, msg); +endfunction + +function Action assert_get_bits_not_eq(Get#(t) g, t expected, String msg) + provisos (Bits#(t, t_sz)); + return assert_av_bits_not_eq(g.get, expected, msg); +endfunction + // // `assert_get_any(..)` collects any value from the given `Get` interface. It // does not assert anything about this action, but provides some syntactic sugar @@ -344,6 +517,10 @@ function Action assert_get_any(Get#(t) g); return assert_av_any(g.get); endfunction +function Action assert_get_bits_any(Get#(t) g); + return assert_av_bits_any(g.get); +endfunction + // // `assert_get_eq_display(..)`/`assert_get_not_eq_display(..)` assert that the // value from a `Get` interface does or does not match an expected result @@ -365,6 +542,19 @@ function Action assert_get_not_eq_display(Get#(t) g, t expected, String msg) return assert_av_not_eq_display(g.get, expected, msg); endfunction +function Action assert_get_bits_eq_display(Get#(t) g, t expected, String msg) + provisos (Bits#(t, t_sz)); + return assert_av_bits_eq_display(g.get, expected, msg); +endfunction + +function Action assert_get_bits_not_eq_display( + Get#(t) g, + t expected, + String msg) + provisos (Bits#(t, t_sz)); + return assert_av_bits_not_eq_display(g.get, expected, msg); +endfunction + // // `assert_get_any_display(..)` collects and display any value from the given // `Get` interface. It does not assert anything about this action, but provides @@ -377,6 +567,11 @@ function Action assert_get_any_display(Get#(t) g) return assert_av_any_display(g.get); endfunction +function Action assert_get_bits_any_display(Get#(t) g) + provisos (Bits#(t, t_sz)); + return assert_av_bits_any_display(g.get); +endfunction + // // `assert_get_eq_display(..)`/`assert_get_not_eq_display(..)` assert that the // value from a `Get` interface does or does not match an expected result diff --git a/hdl/ip/bsv/ignition/BUILD b/hdl/ip/bsv/ignition/BUILD index 43f3dfce..d663721e 100644 --- a/hdl/ip/bsv/ignition/BUILD +++ b/hdl/ip/bsv/ignition/BUILD @@ -34,9 +34,9 @@ bluespec_library('Target', '//hdl/ip/bsv:Strobe', ], using = { + # Bluesim generated C++ output seems to trigger compiler warnings. + # Silence these for now. 'bsc_flags': [ - # Bluesim generated C++ output seems to trigger compiler warnings. - # Silence these for now. '-Xc++', '-Wno-dangling-else', '-Xc++', '-Wno-bool-operation', ], @@ -67,14 +67,14 @@ bluespec_library('TargetWrapper', bluespec_library('Controller', sources = [ 'IgnitionController.bsv', - 'IgnitionEventCounter.bsv', + 'IgnitionControllerCounters.bsv', + 'IgnitionControllerShared.bsv', ], deps = [ ':ControllerRegisters', ':Protocol', ':Transceiver', - '//hdl/ip/bsv:Countdown', - '//hdl/ip/bsv:SchmittReg', + '//hdl/ip/bsv:CounterRAM', '//hdl/ip/bsv:Strobe', ], using = { @@ -122,11 +122,30 @@ bluespec_verilog('mkParser', ], }) -bluespec_verilog('mkTransceiver', - env = 'ignition_target', +bluespec_verilog('mkController', + env = 'ecp5', + top = 'IgnitionController.bsv', + modules = [ + 'mkDefaultController', + 'mkDefaultControllerUsingBRAM', + ], + deps = [ + ':Controller', + ], + local = { + 'bsc_flags': [ + '-opt-undetermined-vals', + '-unspecified-to', 'X', + ], + }) + +bluespec_verilog('mkTransceivers', + env = 'ecp5', top = 'IgnitionTransceiver.bsv', modules = [ - 'mkTransceiver', + 'mkTransceivers6', + 'mkTransceivers8', + 'mkControllerTransceiver36', ], deps = [ ':Transceiver', diff --git a/hdl/ip/bsv/ignition/Ignition Controller Receiver.drawio.svg b/hdl/ip/bsv/ignition/Ignition Controller Receiver.drawio.svg new file mode 100644 index 00000000..93d652b5 --- /dev/null +++ b/hdl/ip/bsv/ignition/Ignition Controller Receiver.drawio.svg @@ -0,0 +1,4 @@ + + + +Deserializer MuxWrite BackReceiveDecode (8B10)WaitDeserializerChannel 0deserializer_events_l2[0]FIFO(1)deserializer_events_l2[1]FIFO(1)deserializer_events_l2[8]FIFO(1)deserializer_events_l1[0]FIFO(1)DeserializerChannel 4DeserializerChannel 7DeserializerChannel 3DeserializerEventDeserializerEventdeserializer_events_l1[1]FIFO(1)deserializer_events_l1[2]FIFO(1)deserializer_events_l0FIFO(1)DeserializerChannel 36DeserializerEventreceive_state_1deserializer_eventBRAM(ReceiveState1 x 36)DeserializerEventdecode(..)Reg#(Maybe#(DeserializerEvent))decoder_eventReg#(Maybe#(DecoderEvent))DecoderEventread(id)Parseparse(..)receive_state_2BRAM(ReceiveState2 x 36)read(id)parser_eventReg#(Maybe#(ParserEvent))ParserEventreceive(..)ReceiverEventreceiver_eventsBRAMFIFO(1023)ReceiverEventNext Receiver StateWrite(..)Reg#(Maybe#(...))Controller (N)............set_polarity_inverted(...)set_search_for_comma(...)request_reset()Sampler 0Sampler 3Sampler 4Sampler 7Sampler 36Receiver Watchdogtick_1khzreceiver_locked \ No newline at end of file diff --git a/hdl/ip/bsv/ignition/Ignition Controller.drawio.svg b/hdl/ip/bsv/ignition/Ignition Controller.drawio.svg new file mode 100644 index 00000000..be172cd6 --- /dev/null +++ b/hdl/ip/bsv/ignition/Ignition Controller.drawio.svg @@ -0,0 +1,4 @@ + + + +receiver_events3N.......TickEvent012....Timer WheelGenerates a TickEvent at 1Hz for each Controller ID, at even spaced intervals. This ensures no conflicting or overlapping TickEvents (ticks are offset in phase) and avoids dense bursts of events downstream of tick event driven logic.ReceiverEventControllerReceiver(N)tick_eventssoftware_requestSPISoftwareRequestFIFO(1)FIFO(1)FIFO(1)01...Npresence01...Ntransceiver01...Nhello_timer01...Ntarget_system_type01...Ntarget_system_status01...Ntarget_system_events01...Ntarget_system_power_request_status01...Ntarget_link0_status01...Ntarget_link0_events01...Ntarget_link1_status01...Ntarget_link1_eventsRegister FileController IDcontroller_idControllerIDevent_handler_selectHandlingTickEventHandlingReceiverEventHandlingSoftwareRequestAwaitingControllerDataValidAwaitingEventRead event dataRead controller data from registersUpdate controller data with event dataWrite controller data back to registersEnqueue software response or transceiver eventUpdate countersSet controller_id from eventSet handler based on event sourceSelect registers for controller with IDStateEvent Handler LogicEvent Sourcessoftware_responseFIFO(1)SoftwareResponseEvents handled in urgency/probability of occurrence; software requests first, followed by ticks and receiver events.transmitter_eventsTransmitterEventFIFO(1)TransmitterEventControllerTransmitter (N)Countable*EventsControllerCountersEvent Sinks \ No newline at end of file diff --git a/hdl/ip/bsv/ignition/IgnitionController.bsv b/hdl/ip/bsv/ignition/IgnitionController.bsv index 49ae62d4..a0126ba6 100644 --- a/hdl/ip/bsv/ignition/IgnitionController.bsv +++ b/hdl/ip/bsv/ignition/IgnitionController.bsv @@ -1,477 +1,1126 @@ package IgnitionController; export Parameters(..); -export ReadVolatile(..); -export LinkEventCounterRegisters(..); -export Registers(..); -export Interrupts(..); -export Status(..); export Controller(..); +export ControllerId(..); +export RegisterId(..); +export RegisterRequest_$op(..); +export RegisterRequest(..); +export RegisterServer(..); +export CounterAddress(..); +export CounterId(..); +export Counter(..); +export CounterServer(..); +export TransmitterOutputEnableMode(..); export mkController; -export registers; -export transceiver_client; -export register_pages; -export tx_enabled; - -// Interrupt helpers. -export interrupts_none; - +export read_controller_register_into; +export clear_controller_counter; +export read_controller_counter_into; +export transceiver_state_value; + +import BRAMCore::*; +import BRAMFIFO::*; +import BuildVector::*; +import ClientServer::*; import ConfigReg::*; import Connectable::*; import DefaultValue::*; -import DReg::*; -import GetPut::*; import FIFO::*; +import FIFOF::*; +import GetPut::*; +import StmtFSM::*; import Vector::*; -import Countdown::*; -import SchmittReg::*; -import Strobe::*; - +import IgnitionControllerCounters::*; import IgnitionControllerRegisters::*; -import IgnitionEventCounter::*; +import IgnitionControllerShared::*; import IgnitionProtocol::*; +import IgnitionReceiver::*; import IgnitionTransceiver::*; +import IgnitionTransmitter::*; typedef struct { + Integer tick_period; + Integer transmitter_output_disable_timeout; IgnitionProtocol::Parameters protocol; } Parameters; instance DefaultValue#(Parameters); defaultValue = Parameters { + tick_period: 1000, + transmitter_output_disable_timeout: 100, protocol: defaultValue}; endinstance -typedef struct { -} Interrupts deriving (Bits, Eq, FShow); - -interface ReadVolatile#(type t); - method ActionValue#(t) _read(); -endinterface - -interface LinkEventCounterRegisters; - interface Reg#(IgnitionControllerRegisters::LinkEvents) summary; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) encoding_error; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) decoding_error; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) ordered_set_invalid; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) message_version_invalid; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) message_type_invalid; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) message_checksum_invalid; -endinterface - -interface Registers; - interface Reg#(IgnitionControllerRegisters::ControllerState) controller_state; - interface ReadOnly#(IgnitionControllerRegisters::LinkStatus) controller_link_status; - interface ReadOnly#(IgnitionControllerRegisters::TargetSystemType) target_system_type; - interface ReadOnly#(IgnitionControllerRegisters::TargetSystemStatus) target_system_status; - interface ReadOnly#(IgnitionControllerRegisters::TargetSystemFaults) target_system_faults; - interface ReadOnly#(IgnitionControllerRegisters::TargetRequestStatus) target_request_status; - interface ReadOnly#(IgnitionControllerRegisters::LinkStatus) target_link0_status; - interface ReadOnly#(IgnitionControllerRegisters::LinkStatus) target_link1_status; - interface Reg#(IgnitionControllerRegisters::TargetRequest) target_request; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) controller_status_received_count; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) controller_hello_sent_count; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) controller_request_sent_count; - interface ReadVolatile#(IgnitionControllerRegisters::Counter) controller_message_dropped_count; - interface LinkEventCounterRegisters controller_link_counters; - interface LinkEventCounterRegisters target_link0_counters; - interface LinkEventCounterRegisters target_link1_counters; -endinterface +typedef enum { + TransceiverState = 0, + ControllerState, + TargetSystemType, + TargetSystemStatus, + TargetSystemEvents, + TargetSystemPowerRequestStatus, + TargetLink0Status, + TargetLink1Status +} RegisterId deriving (Bits, Eq, FShow); typedef struct { - Bool always_transmit; - Bool target_present; - Bool receiver_locked; -} Status deriving (Bits); - -interface Controller; - interface TransceiverClient txr; - interface Registers registers; - interface Reg#(Interrupts) interrupts; - interface PulseWire tick_1khz; - (* always_enabled *) method Status status(); + ControllerId#(n) id; + RegisterId register; + union tagged { + void Read; + Bit#(8) Write; + } op; +} RegisterRequest#(numeric type n) deriving (Bits, FShow); + +typedef Server#(RegisterRequest#(n), Bit#(8)) RegisterServer#(numeric type n); + +typedef enum { + Disabled = 0, + EnabledWhenReceiverAligned = 1, + EnabledWhenTargetPresent = 2, + AlwaysEnabled = 3 +} TransmitterOutputEnableMode deriving (Bits, Eq, FShow); + +// An interface for a Controller with `n` "channels" which processes events +// submitted by a receiver, answers requests from upstack software and generates +// events for a transmitter. +interface Controller #(numeric type n); + // Strobe used to drive timers and generate internal events. + method Action tick_1mhz(); + + // Transceiver interface + interface ControllerTransceiverClient#(n) txr; + + // Software interface + interface RegisterServer#(n) registers; + interface CounterServer#(n) counters; + // A bit vector indicating which channels have a `Target` present. + method Vector#(n, Bool) presence_summary(); + + // Flag indicating whether or not the Controller is idle, meaning it is not + // processing software requests, tick events, receiver events or + // incrementing counters. + (* always_ready *) method Bool idle(); endinterface -module mkController #(Parameters parameters) (Controller); +// +// mkController +// +// This is the second iteration of the Ignition Controller and an implementation +// of the multi-channel `Controller(..)` interface using a series of RAM +// elements to track the state of `n` Target systems. The first iteration of the +// Controller implemented all logic per channel, only time-multiplexing some +// parts of the receiver, resulting in very high device utilization of the ECP5 +// FPGA running the design when synthesizing up to 36 copies. This second +// iteration in contrast aims to do as much work in a serial and +// time-multiplexed manner by making use of the relatively slow baudrate of the +// link between Controller and Target. This results in far fewer copies of most +// logic blocks resulting in much lower FPGA device utilization while expanding +// the feature set. +// +// The following calculations are used to justify this design: +// +// Both the Controller and Target are operating at a 50 MHz design clock. +// Messages are exchanged between the systems using an 8B10B encoded serial +// link, operating at 10 MBit/s. Message bytes are encoded using 10 bit symbols +// resulting in a symbol rate of 1M symbols/s/receiver, or a combined 36M +// symbols/s for the current maximum of 36 channels. +// +// The receiver is pipelined such that it deserializes bits into symbols in +// parallel and then processes all symbols in serial at a rate of one +// symbol/clock cycle. At a 50 MHz design clock this results in a peak sustained +// throughput of 50M symbols/s, which is well below the required 36M symbols/s +// generated by all 36 receivers. Note that due to clock jitter between the two +// systems a symbol period of 50 cycles is not strictly true and some symbols +// may last 49 or 51 clock cycles. But 50 cycles makes for convenient math and +// is expected to be true on average. +// +// Not all symbols however need to be processed by the Controller as it is only +// concerned with Target data. Framing and clock recovery symbols transmitted +// between messages are only useful to the receiver and can be dropped once +// observed. +// +// The current transmitter implementation will transmit at most three Target +// Status messages back to back after which it is required to transmit an Idle1 +// and Idle2 ordered set consisting of two symbols each for clock recovery +// purposes. Each Status message consists of a start symbol, version and message +// types symbols, eight data symbols, a checksum symbol and two end of message +// symbols for a total of 12 symbols. See the `IgnitionProtocol` package and +// `TransmitterTests` for more details. +// +// For each Status message received the receiver emits eight symbol events and a +// nineth "message received" event to the Controller. For a sequence of three +// Status messages followed by two Idle sets making up 40 transmitted symbols +// the receiver therefor emits 27 events to the Controller. Given a symbol rate +// of 1M symbol/s each receiver can generate 675k events/s and 36 such receivers +// can generate at total of 24.3M events/s. +// +// Each event emitted by the receiver is handled in a fixed three clock cycles +// by the Controller. Operating at 50 MHz it can thus process up to 16.66M +// event/s. This falls short of the combined 24.3M events which can be +// generated, but this would be a worst case scenario where every Target would +// generate Status messages at line rate. +// +// In practice we expect each Target to emit less than half of that number of +// messages and certainly not for a sustained period of time. The expectation is +// that with some appropriately sized FIFOs between the receiver and Controller +// it can keep up with realistic workload. Note that the Controller generates an +// additional 1000 tick events/channel used to generate timeout events and needs +// cycles to respond to software requests, but these are expected to consume at +// most 1-2% of available event cycles leaving enough allocated for receiver +// events. +// +// If more event processing throughput is required or if the number of channels +// is increased a second copy of the Controller could be instantiated with each +// handling half of the channels. This would double aggregate event processing +// throughput. The cost of an additional Controller handling half the channels +// and splitting the transceivers may be acceptable. +// +// Additional design details are explained below when logic elements are +// declared. +// +module mkController #( + Parameters parameters, + Bool zero_registers) + (Controller#(n)); + let status_message_timeout_reset_value = + fromInteger(parameters.protocol.status_interval + 1); + + // + // Event handler FIFOs // - // External pulse used to generate timed events. + // FIFOs used to receive software requests, internally generated tick events + // and receiver events respectively. These FIFOs have an guarded enq side + // and unguarded deq side. This allows the enq side to be connected to + // external logic with the implicit guards providing automatic backpressure + // while the deq side of all three FIFOs can be used in a single rule + // without those rules getting (implicitely) blocked by the scheduler if one + // of the FIFOs is empty. // - Reg#(Bool) tick <- mkDReg(False); - - Reg#(LinkStatus) link_status <- mkRegU(); - Wire#(Message) rx <- mkWire(); - FIFO#(Message) tx <- mkLFIFO(); - - // `CountingMonitors` keep track of link events occuring on the local - // receiver as well as the two receivers on the `Target` side. - CountingMonitor link_monitor <- mkCountingMonitor(); - CountingMonitor target_link0_monitor <- mkCountingMonitor(); - CountingMonitor target_link1_monitor <- mkCountingMonitor(); - - // Additional Message counters. - Counter n_status_received <- mkCounter(); - Counter n_hello_sent <- mkCounter(); - Counter n_request_sent <- mkCounter(); - Counter n_message_dropped <- mkCounter(); - - // `Target` present indicator. This is implemented using a filter, requiring - // three Status messages to be received before the `Target` is marked - // present. - Reg#(Bool) past_target_present <- mkReg(False); - SchmittReg#(3, Bool) target_present <- - mkSchmittReg(False, EdgePatterns { - negative_edge: 'b100, - positive_edge: 'b111, - mask: 'b111}); - - // The latest `Status` `Message` received from a `Target`. This should only - // be considered valid if the `target_present` flag above is True. - Reg#(Message) status_message <- mkRegU(); - - // A pending `Request`, received upstream (software). - ConfigReg#(Maybe#(Request)) pending_request <- mkConfigReg(tagged Invalid); - - // Settable override to make the Controller always transmit rather than wait - // for a Target to be present first. - ConfigReg#(Bool) always_transmit <- mkConfigReg(False); + FIFOF#(RegisterRequest#(n)) software_request <- mkGFIFOF(False, True); + FIFOF#(ControllerId#(n)) tick_events <- mkGFIFOF(False, True); + FIFOF#(ReceiverEvent#(n)) receiver_events <- mkGFIFOF(False, True); // - // Events + // Output FIFOs holding software responses and transmitter events (outgoing + // messages). Note that these FIFOs implicitly guard the event handler rules + // causing the handler to block if they are not emptied by downstream logic + // in a timely fashion. // - PulseWire message_accepted <- mkPulseWire(); + FIFOF#(Bit#(8)) software_response <- mkFIFOF(); + FIFOF#(TransmitterEvent#(n)) transmitter_events <- mkFIFOF(); - PulseWire status_received <- mkPulseWire(); - Countdown#(6) status_update_expired <- mkCountdownBy1(); + let event_handler_idle = + !(software_request.notEmpty || + tick_events.notEmpty || + receiver_events.notEmpty); - Countdown#(6) hello_expired <- mkCountdownBy1(); - Reg#(Bool) hello_requested <- mkReg(True); + // + // Register files holding the per-Controller state + // + // Upon receiving an event the Controller is expected to select the + // registers in the register files according to the controller id attached + // to the event. The handler then waits one cycle for the RAM outputs to + // become valid, handles the event on the third cycle and writes back the + // registers while the next event is selected. This allows an event to be + // processed every three cycles. + // + // In order to keep the state in the receiver as minimal as possible + // multi-byte Status messages sent by Targets are streamed to the Controller + // one byte at the time as they are decoded and parsed. But the receiver + // does not yet know if the whole Status message is valid until it has + // decoded and parsed all bytes and the required checksum. In order to store + // these in-progress Status messages, some register files double buffer + // their data using an active/inactive value slot. + // + // The bytes for an incoming Status message are written to the inactive + // value slots, while software requests for the Status data are read from + // the active value slots. Once the receiver has successfully parsed the + // whole message it emits an event indicating so, at which point the buffer + // pointer stored in the `presence` register is updated. The Controller + // state is updated with the now active values and subsequent software + // requests will read the new Status data. + // + // If a complete and valid Status message is not received by the receiver + // the buffer pointer is never updated and the next Status message will + // simply overwrite the partial previous message in inactive slots. + // + RegisterFile#(n, PresenceRegister) presence <- mkBRAMRegisterFile(); + RegisterFile#(n, TransceiverRegister) transceiver <- mkBRAMRegisterFile(); + RegisterFile#(n, HelloTimerRegister) hello_timer <- mkBRAMRegisterFile(); + BufferedValueRegisterFile#(n, SystemType) + target_system_type <- mkBRAMRegisterFile(); + BufferedValueRegisterFile#(n, SystemStatus) + target_system_status <- mkBRAMRegisterFile(); + BufferedValueRegisterFile#(n, SystemFaults) + target_system_events <- mkBRAMRegisterFile(); + BufferedValueRegisterFile#(n, RequestStatus) + target_system_power_request_status <- mkBRAMRegisterFile(); + BufferedValueRegisterFile#(n, LinkStatus) + target_link0_status <- mkBRAMRegisterFile(); + BufferedValueRegisterFile#(n, LinkEvents) + target_link0_events <- mkBRAMRegisterFile(); + BufferedValueRegisterFile#(n, LinkStatus) + target_link1_status <- mkBRAMRegisterFile(); + BufferedValueRegisterFile#(n, LinkEvents) + target_link1_events <- mkBRAMRegisterFile(); + + // While updating the presence flag for each Target, a read-only copy is + // kept in a summary vector, which can be read by software to determine + // which channels to query for data. + Vector#(n, Reg#(Bool)) presence_summary_r <- replicateM(mkConfigReg(False)); + + ControllerCounters#(n) event_counters <- mkControllerCounters(); + + // Event handler state + Reg#(EventHandlerState) event_handler_state <- mkReg(AwaitingEvent); + Reg#(EventHandlerState) event_handler_select <- mkRegU(); + Reg#(ControllerId#(n)) controller_id <- mkRegU(); + + PulseWire tick <- mkPulseWire(); + Reg#(UInt#(10)) tick_count <- mkReg(0); // - // Connect the global tick + // Register file init + // + // The register files may be initialized with random data upon reset. This + // reset sequence will reset all registers to zero and then not run again. + // If this module is part of a design targeting a device which clears BRAMs + // on PoR, `init` can be set to False during elaboration which will optimize + // this away. // + Reg#(Bool) init_complete <- mkReg(!zero_registers); + + if (zero_registers) begin + Reg#(ControllerId#(n)) i <- mkReg(0); + + FSM init_seq <- mkFSMWithPred(seq + repeat(fromInteger(valueof(n))) action + // Select the registers for each Controller in sequence.. + presence.select(i); + transceiver.select(i); + hello_timer.select(i); + + target_system_type.select(i); + target_system_status.select(i); + target_system_events.select(i); + target_system_power_request_status.select(i); + target_link0_status.select(i); + target_link0_events.select(i); + target_link1_status.select(i); + target_link1_events.select(i); + + // .. and reset the their values. + // + // A read-modify-write sequence is not needed so the select and + // write-back of the registers can happen in the same cycle. + presence <= unpack('0); + transceiver <= unpack('0); + hello_timer <= unpack('0); + + target_system_type <= unpack('0); + target_system_status <= unpack('0); + target_system_events <= unpack('0); + target_system_power_request_status <= unpack('0); + target_link0_status <= unpack('0); + target_link0_events <= unpack('0); + target_link1_status <= unpack('0); + target_link1_events <= unpack('0); + + i <= i + 1; + endaction + init_complete <= True; + endseq, !init_complete); + + (* fire_when_enabled *) + rule do_init (!init_complete); + init_seq.start(); + endrule + end + // + // Generate Tick events + // + // Each Controller channel requires a 1 KHz tick in order to generate + // timeouts and send periodic Hello messages. These ticks are generated + // using a single counter and the rules below, enqueueing Tick events for + // each channel into a FIFO when appropriate. + // + // These Tick events are handled by the event handler with relatively high + // priority, so in order to not starve other events from being handled by a + // burst of Tick events for many channels, the ticks for the different + // channels are generated with a phase offset by dividing the available 1000 + // microseconds by `n` channels. + // + // Note that in order to reduce the time between ticks during simulation the + // total tick period which gets divided can be configured through the + // `Parameters` used when constructing the `Controller`. + // (* fire_when_enabled *) - rule do_tick (tick); - status_update_expired.send(); - hello_expired.send(); + rule do_tick (init_complete && tick); + let wrap = tick_count == fromInteger(parameters.tick_period - 1); + tick_count <= wrap ? 0 : tick_count + 1; endrule + let tick_phase_shift = parameters.tick_period / valueof(n); + + // Per channel rule which generates a Tick event for the given channel when + // the counter hits the count for the determined phase offset. + for (Integer i = 0; i < valueof(n); i = i + 1) begin + (* fire_when_enabled *) + rule do_enq_tick_event ( + init_complete && + tick && + tick_count == fromInteger(i * tick_phase_shift)); + tick_events.enq(fromInteger(i)); + endrule + end + + // + // Event handler rules + // + + function Action count_application_events( + CountableApplicationEvents events) = + event_counters.count_application_events(controller_id, events); + + function Action count_transceiver_events( + CountableTransceiverEvents events) = + event_counters.count_transceiver_events(controller_id, events); + (* fire_when_enabled *) - rule do_update_target_presence; - if (status_received) - target_present <= True; - else if (status_update_expired) begin - target_present <= False; - $display("%5t [Controller] Target Status timeout", $time); + rule do_start_event_handler ( + init_complete && + event_handler_state == AwaitingEvent); + // Get the Controller id for the next pending event. If no event is + // pending the `Invalid` variant will keep the event handler waiting + // until one arrives. + let maybe_controller_id = tagged Invalid; + + if (software_request.notEmpty) begin + event_handler_select <= HandlingSoftwareRequest; + maybe_controller_id = tagged Valid software_request.first.id; end - - if (status_received || status_update_expired) begin - status_update_expired <= - fromInteger(parameters.protocol.status_interval + 2); + else if (tick_events.notEmpty) begin + event_handler_select <= HandlingTickEvent; + maybe_controller_id = tagged Valid tick_events.first; + end + else if (receiver_events.notEmpty) begin + event_handler_select <= HandlingReceiverEvent; + maybe_controller_id = tagged Valid receiver_events.first.id; end - past_target_present <= target_present; - - if (past_target_present != target_present) begin - let format = target_present ? - "%5t [Controller] Target present" : - "%5t [Controller] Target not present"; - $display(format, $time); + // If an event is pending, request the Controller state to be read by + // setting the id in all register files. This will automatically cause a + // read of each register file on the next clock cycle. + if (maybe_controller_id matches tagged Valid .id) begin + controller_id <= id; + + presence.select(id); + transceiver.select(id); + hello_timer.select(id); + + target_system_type.select(id); + target_system_status.select(id); + target_system_events.select(id); + target_system_power_request_status.select(id); + target_link0_status.select(id); + target_link0_events.select(id); + target_link1_status.select(id); + target_link1_events.select(id); + + event_handler_state <= AwaitingControllerDataValid; end endrule (* fire_when_enabled *) - rule do_receive_status_message (rx matches tagged Status .*); - message_accepted.send(); - status_message <= rx; - - // Update the counters tracking Target side link events. - target_link0_monitor.monitor(rx.Status.link0_events); - target_link1_monitor.monitor(rx.Status.link1_events); - - n_status_received.send(); - status_received.send(); - - $display( - "%5t [Controller] Received ", $time, - message_status_pretty_format(rx)); + rule do_read_registers ( + init_complete && + event_handler_state == AwaitingControllerDataValid); + // Select the appropriate handler. + event_handler_state <= event_handler_select; endrule (* fire_when_enabled *) - rule do_request_hello (!hello_requested && hello_expired); - hello_requested <= True; + rule do_handle_software_request ( + init_complete && + software_request.notEmpty && + event_handler_state == HandlingSoftwareRequest); + function Action respond_with(value_t value) + provisos ( + Bits#(value_t, value_t_sz), + Add#(value_t_sz, a__, 8), + FShow#(value_t)) = + software_response.enq(extend(pack(value))); + + function Action respond_with_current( + RegisterFile#(n, Vector#(2, value_t)) file) + provisos ( + Bits#(value_t, value_t_sz), + Add#(value_t_sz, a__, 8), + DefaultValue#(value_t)) = + software_response.enq(extend(pack( + presence.present ? + file[presence.current_status_message] : + defaultValue))); + + case (tuple2( + software_request.first.op, + software_request.first.register)) matches + {tagged Read, TransceiverState}: + respond_with(pack(transceiver)[5:0]); + + {tagged Write .b, TransceiverState}: begin + TransceiverRegister request = unpack(extend(b)); + TransceiverRegister transceiver_ = transceiver; + + transceiver_.transmitter_output_enable_mode = + request.transmitter_output_enable_mode; + + case (request.transmitter_output_enable_mode) + Disabled: begin + transceiver_.transmitter_output_disable_timeout_ticks_remaining = 0; + transceiver_.transmitter_output_enabled = False; + end + + EnabledWhenReceiverAligned: + if (transceiver.receiver_status.receiver_aligned) begin + transceiver_.transmitter_output_disable_timeout_ticks_remaining = 0; + transceiver_.transmitter_output_enabled = True; + end + else if (transceiver.transmitter_output_enabled && + transceiver.transmitter_output_disable_timeout_ticks_remaining == 0) begin + transceiver_.transmitter_output_disable_timeout_ticks_remaining = + fromInteger(parameters.transmitter_output_disable_timeout); + end + + EnabledWhenTargetPresent: + if (presence.present) begin + transceiver_.transmitter_output_disable_timeout_ticks_remaining = 0; + transceiver_.transmitter_output_enabled = True; + end + else if (transceiver.transmitter_output_enabled && + transceiver.transmitter_output_disable_timeout_ticks_remaining == 0) begin + transceiver_.transmitter_output_disable_timeout_ticks_remaining = + fromInteger(parameters.transmitter_output_disable_timeout); + end + + AlwaysEnabled: begin + transceiver_.transmitter_output_disable_timeout_ticks_remaining = 0; + transceiver_.transmitter_output_enabled = True; + end + endcase + + $display("%5t [Controller %02d] Transmitter output enable mode ", + $time, + controller_id, + fshow(transceiver_.transmitter_output_enable_mode)); + + if (transceiver.transmitter_output_enabled && + !transceiver_.transmitter_output_enabled) begin + $display("%5t [Controller %02d] Transmitter output disabled", + $time, + controller_id); + end + else if (!transceiver.transmitter_output_enabled && + transceiver_.transmitter_output_enabled) begin + $display("%5t [Controller %02d] Transmitter output enabled", + $time, + controller_id); + end + + // Update the transceiver register. Any changes are applied on + // the next tick event. See `do_handle_tick_event`. + transceiver <= transceiver_; + end + + {tagged Read, ControllerState}: + respond_with(presence.present); + + {tagged Read, TargetSystemType}: + respond_with_current(target_system_type); + + {tagged Read, TargetSystemStatus}: + respond_with_current(target_system_status); + + {tagged Read, TargetSystemEvents}: + respond_with_current(target_system_events); + + {tagged Read, TargetSystemPowerRequestStatus}: + respond_with_current( + target_system_power_request_status); + + {tagged Write .b, TargetSystemPowerRequestStatus}: begin + let maybe_request = + case (b[5:4]) + 1: tagged Valid SystemPowerOff; + 2: tagged Valid SystemPowerOn; + 3: tagged Valid SystemPowerReset; + default: tagged Invalid; + endcase; + + if (presence.present &&& + maybe_request matches tagged Valid .request) begin + $display("%5t [Controller %02d] Requesting ", + $time, + controller_id, fshow(request)); + + transmitter_events.enq( + TransmitterEvent { + id: controller_id, + ev: tagged Message tagged Request request}); + + count_application_events( + CountableApplicationEvents { + status_received: False, + status_timeout: False, + target_present: False, + target_timeout: False, + hello_sent: False, + system_power_request_sent: True}); + end + end + + {tagged Read, TargetLink0Status}: + respond_with_current(target_link0_status); + + {tagged Read, TargetLink1Status}: + respond_with_current(target_link1_status); + endcase + + software_request.deq(); + event_handler_state <= AwaitingEvent; endrule (* fire_when_enabled *) - rule do_handle_hello_expired (hello_requested && !isValid(pending_request)); - tx.enq(tagged Hello); + rule do_handle_tick_event ( + init_complete && + tick_events.notEmpty && + event_handler_state == HandlingTickEvent); + let status_timeout = False; + let target_timeout = False; + let hello_sent = False; + + // Copy the `presence` and `transceiver` registers so indiviual fields + // can be updated as appropriate. + let presence_ = presence; + let transceiver_ = transceiver; + + // Update the presence history if a Status message timeout occures. + if (presence.status_message_timeout_ticks_remaining == 0) begin + $display("%5t [Controller %02d] Target Status timeout", + $time, + controller_id); + + // Add a timeout to the presence history. + presence_.history = shiftInAt0(presence.history, False); + + // Reset the timeout counter. + presence_.status_message_timeout_ticks_remaining = + status_message_timeout_reset_value; + + status_timeout = True; + end + // Count down the Status message timeout counter. + else begin + presence_.status_message_timeout_ticks_remaining = + presence.status_message_timeout_ticks_remaining - 1; + end - n_hello_sent.send(); - hello_expired <= fromInteger(parameters.protocol.hello_interval); - hello_requested <= False; + // Update the filtered presence bit given the new history. + if (pack(presence_.history) == 'b000 && presence.present) begin + $display("%5t [Controller %02d] Target not present", + $time, + controller_id); - $display("%5t [Controller] Sent Hello", $time); - endrule + target_timeout = True; + presence_.present = False; + end - (* fire_when_enabled *) - rule do_send_request (pending_request matches tagged Valid .request); - tx.enq(tagged Request request); + // Write back the presence state. + presence <= presence_; + presence_summary_r[controller_id] <= presence_.present; - n_request_sent.send(); - pending_request <= tagged Invalid; + // Count down until the next Hello. + if (hello_timer.ticks_remaining == 0) begin + hello_timer.ticks_remaining <= + fromInteger(parameters.protocol.hello_interval); + end + else begin + hello_timer.ticks_remaining <= hello_timer.ticks_remaining - 1; + end - $display("%5t [Controller] Sent Request ", $time, fshow(request)); - endrule + // Count down the transmitter disable timeout if the counter is active. + if (transceiver.transmitter_output_disable_timeout_ticks_remaining != 0) begin + transceiver_.transmitter_output_disable_timeout_ticks_remaining = + transceiver.transmitter_output_disable_timeout_ticks_remaining - 1; + end + // Start the disable timeout counter if the Target has been declared not + // present. + else if (transceiver.transmitter_output_enable_mode == + EnabledWhenTargetPresent && + presence.present && + !presence_.present) begin + transceiver_.transmitter_output_disable_timeout_ticks_remaining = + fromInteger(parameters.transmitter_output_disable_timeout); + end - (* fire_when_enabled *) - rule do_drop_hello_message (rx matches tagged Hello); - message_accepted.send(); - n_message_dropped.send(); - $display("%5t [Controller] Hello dropped", $time); - endrule + // Disable the transmitter output if the transmitter disable output + // timer expires. A count of one is used as the timeout here since this + // does not have to be precise and allows a value of zero to indicate + // the counter is not active. + if (transceiver.transmitter_output_enabled && + transceiver.transmitter_output_disable_timeout_ticks_remaining == 1) begin + $display("%5t [Controller %02d] Transmitter output disabled", + $time, + controller_id); + + transceiver_.transmitter_output_enabled = False; + end - (* fire_when_enabled *) - rule do_drop_request_message (rx matches tagged Request .request); - message_accepted.send(); - n_message_dropped.send(); - $display("%5t [Controller] Request ", $time, fshow(request), " dropped"); - endrule + // Write back the transceiver state. + transceiver <= transceiver_; - let system_type = - TargetSystemType { - system_type: pack(status_message.Status.system_type.id)}; + // Transmit an Hello message if the Hello timer expires. + if (hello_timer.ticks_remaining == 0) begin + $display("%5t [Controller %02d] Hello", + $time, + controller_id); - let target_system_status = target_present ? - pack(status_message.Status.system_status) : - '0; + transmitter_events.enq( + TransmitterEvent { + id: controller_id, + ev: tagged Message tagged Hello}); - interface TransceiverClient txr; - interface GetS to_txr = fifoToGetS(tx); + hello_sent = True; + end + // Transmit an OutputEnable event otherwise. These are sent every tick + // so that even if a state change in the output enable is requested + // during the same tick as the Hello message above the state will + // converge at most one tick later. + else begin + transmitter_events.enq( + TransmitterEvent { + id: controller_id, + ev: tagged OutputEnabled + transceiver_.transmitter_output_enabled}); + end - interface PutS from_txr; - method offer = rx._write; - method accepted = message_accepted; - endinterface + // Enqueue a request to update the appropriate counters. + if (status_timeout || target_timeout || hello_sent) begin + count_application_events( + CountableApplicationEvents { + status_received: False, + status_timeout: status_timeout, + target_present: False, + target_timeout: target_timeout, + hello_sent: hello_sent, + system_power_request_sent: False}); + end - method Action monitor(LinkStatus status, LinkEvents events); - link_status <= status; - link_monitor.monitor(events); - endmethod + // Complete the tick event. + tick_events.deq(); + event_handler_state <= AwaitingEvent; + endrule - method tick_1khz = tick; - endinterface + (* fire_when_enabled *) + rule do_handle_receiver_event ( + init_complete && + receiver_events.notEmpty && + event_handler_state == HandlingReceiverEvent); + case (receiver_events.first.ev) matches + tagged TargetStatusReceived: begin + $display("%5t [Controller %02d] Received Status", + $time, + controller_id); + + PresenceRegister presence_ = presence; + Bool target_present = False; + + // Make the last Status message active by flipping the + // Status message pointer. + presence_.current_status_message = + presence.current_status_message + 1; + + // Reset the Status timeout counter. + presence_.status_message_timeout_ticks_remaining = + status_message_timeout_reset_value; + + // Update the presence history. + presence_.history = shiftInAt0(presence.history, True); + + if (pack(presence_.history) == 3'b001 && + !presence.present) begin + $display("%5t [Controller %02d] Target present", + $time, + controller_id); + + presence_.present = True; + target_present = True; + end + + // Write back the presence state. + presence <= presence_; + presence_summary_r[controller_id] <= presence_.present; + + // Update the transmitter output enabled state if configured. + // The actual TransmitterEvent will be sent on the next tick. + // See the Tick handler. + TransceiverRegister transceiver_ = transceiver; + + if (transceiver.transmitter_output_enable_mode == + EnabledWhenTargetPresent && + !presence.present && + presence_.present) begin + $display("%5t [Controller %02d] Transmitter output enabled", + $time, + controller_id); + + transceiver_.transmitter_output_enabled = True; + transceiver_.transmitter_output_disable_timeout_ticks_remaining = 0; + end + + transceiver <= transceiver_; + + count_application_events( + CountableApplicationEvents { + status_received: True, + status_timeout: False, + target_present: target_present, + target_timeout: False, + hello_sent: False, + system_power_request_sent: False}); + + // Request the Target link events to be counted. + let previous = presence.current_status_message; + let current = presence_.current_status_message; + + event_counters.count_target_link0_events( + controller_id, + determine_transceiver_events( + target_link0_status[previous], + target_link0_status[current], + target_link0_events[current], + // Attempt to detect receiver resets by + // comparing the current and previous receiver + // status. This is not an accurate count since + // this will only count instances where the + // status changes as a result of the reset. + // Subsequent resets may happen without the + // status changing, which will go unnoticed + // here. But for the purposes of remote + // monitoring the Target this is good enough. + True)); + + event_counters.count_target_link1_events( + controller_id, + determine_transceiver_events( + target_link1_status[previous], + target_link1_status[current], + target_link1_events[current], + True)); + end + + tagged StatusMessageFragment .field: begin + // Write the non-active value slot of a Status message field + // register. + function Action write_status_message_field( + BufferedValueRegisterFile#(n, t) register, + t value) + provisos (Bits#(t, t_sz)) = + action + let both_values = register; + + // Update the non-active value slot in the register. + if (presence.current_status_message == 0) + both_values[1] = value; + else + both_values[0] = value; + + // Write back the register. + register <= both_values; + endaction; + + case (field) matches + tagged SystemType .system_type: + write_status_message_field( + target_system_type, + system_type); + + tagged SystemStatus .system_status: + write_status_message_field( + target_system_status, + system_status); + + tagged SystemEvents .system_events: + write_status_message_field( + target_system_events, + system_events); + + tagged SystemPowerRequestStatus + .system_power_request_status: + write_status_message_field( + target_system_power_request_status, + system_power_request_status); + + tagged Link0Status .link0_status: + write_status_message_field( + target_link0_status, + link0_status); + + tagged Link0Events .link0_events: + write_status_message_field( + target_link0_events, + link0_events); + + tagged Link0Status .link1_status: + write_status_message_field( + target_link1_status, + link1_status); + + tagged Link0Events .link1_events: + write_status_message_field( + target_link1_events, + link1_events); + endcase + end + + tagged ReceiverReset: begin + $display("%5t [Controller %02d] Receiver reset", + $time, + controller_id); + + TransceiverRegister transceiver_ = transceiver; + + transceiver_.receiver_status = link_status_none; + + if (transceiver.transmitter_output_enable_mode == + EnabledWhenReceiverAligned && + transceiver.transmitter_output_enabled && + transceiver.transmitter_output_disable_timeout_ticks_remaining == 0) begin + // A ReceiverReset means the receiver is not aligned anymore + // and the disable timeout counter should be started. + transceiver_.transmitter_output_disable_timeout_ticks_remaining = + fromInteger(parameters.transmitter_output_disable_timeout); + end + + // Write back the transceiver state. + transceiver <= transceiver_; + + count_transceiver_events( + countable_transceiver_events_receiver_reset); + end + + tagged ReceiverStatusChange .current_status: begin + TransceiverRegister transceiver_ = transceiver; + + transceiver_.receiver_status = current_status; + + if (transceiver.transmitter_output_enable_mode == + EnabledWhenReceiverAligned && + !transceiver.transmitter_output_enabled && + current_status.receiver_aligned) begin + $display("%5t [Controller %02d] Transmitter output enabled", + $time, + controller_id); + + transceiver_.transmitter_output_enabled = True; + transceiver_.transmitter_output_disable_timeout_ticks_remaining = 0; + end + + // Write back the transceiver state. + transceiver <= transceiver_; + + count_transceiver_events( + determine_transceiver_events( + transceiver.receiver_status, + current_status, + defaultValue, + // Do not attempt to infer receiver resets from the + // link status as that would result in the wrong + // count. The receiver may reset multiple times + // without the status changing, which would result + // in those events not being counted. The + // `ReceiverReset` event above is the more accurate + // way to update this counter. + False)); + end + + tagged ReceiverEvent .events: begin + count_transceiver_events( + determine_transceiver_events( + defaultValue, + defaultValue, + events, + False)); + end + endcase + + // Complete the receiver event. + receiver_events.deq(); + event_handler_state <= AwaitingEvent; + endrule - interface Registers registers; - interface Reg controller_state; - method _read = ControllerState { - target_present: pack(target_present), - always_transmit: pack(always_transmit)}; - - method Action _write(ControllerState state); - always_transmit <= unpack(state.always_transmit); - endmethod - endinterface - - interface ReadOnly controller_link_status = - valueToReadOnly( - IgnitionControllerRegisters::LinkStatus { - receiver_aligned: pack(link_status.receiver_aligned), - receiver_locked: pack(link_status.receiver_locked), - polarity_inverted: pack(link_status.polarity_inverted)}); - - interface ReadOnly target_system_type = - castToReadOnlyIf( - target_present, - status_message.Status.system_type, - defaultValue); - - interface ReadOnly target_system_status = - castToReadOnlyIf( - target_present, - status_message.Status.system_status, - defaultValue); - - interface ReadOnly target_system_faults = - castToReadOnlyIf( - target_present, - status_message.Status.system_faults, - defaultValue); - - interface ReadOnly target_request_status = - castToReadOnlyIf( - target_present, - status_message.Status.request_status, - defaultValue); - - interface ReadOnly target_link0_status = - castToReadOnlyIf( - target_present, - status_message.Status.link0_status, - defaultValue); - - interface ReadOnly target_link1_status = - castToReadOnlyIf( - target_present, - status_message.Status.link1_status, - defaultValue); - - interface Reg target_request; - method TargetRequest _read(); - let kind = isValid(pending_request) ? - pack(fromMaybe(?, pending_request)) : 0; - return TargetRequest { - kind: pack(kind), - pending: pack(isValid(pending_request))}; - endmethod - - method Action _write(TargetRequest request) if (!isValid(pending_request)); - let request_ = - case (request.kind) - 1: tagged Valid SystemPowerOff; - 2: tagged Valid SystemPowerOn; - 3: tagged Valid SystemReset; - default: tagged Invalid; - endcase; - pending_request <= request_; - - if (request_ matches tagged Valid .r) - $display( - "%5t [Controller] ", $time, - fshow(r), " Request pending"); - else - $display("%5t [Controller] Request kind %2d ignored", $time); - endmethod - endinterface - - interface ReadVolatile controller_status_received_count = readCounter(n_status_received); - interface ReadVolatile controller_hello_sent_count = readCounter(n_hello_sent); - interface ReadVolatile controller_request_sent_count = readCounter(n_request_sent); - interface ReadVolatile controller_message_dropped_count = readCounter(n_message_dropped); - interface LinkEventCounterRegisters controller_link_counters = asLinkEventCounterRegisters(link_monitor); - interface LinkEventCounterRegisters target_link0_counters = asLinkEventCounterRegisters(target_link0_monitor); - interface LinkEventCounterRegisters target_link1_counters = asLinkEventCounterRegisters(target_link1_monitor); + interface ControllerTransceiverClient txr; + interface Get tx = toGet(transmitter_events); + interface Put rx = toPut(receiver_events); endinterface - interface Reg interrupts; - method _read = defaultValue; - method Action _write(Interrupts i); - endmethod + interface Server registers; + interface Put request = toPut(software_request); + interface Get response = toGet(software_response); endinterface - interface PulseWire tick_1khz; - method _read = tick; - method Action send(); - tick <= True; - endmethod - endinterface + interface Server counters = event_counters.counters; + + method presence_summary = readVReg(presence_summary_r); + + method tick_1mhz = tick.send; - method status = Status { - receiver_locked: link_status.receiver_locked, - target_present: target_present, - always_transmit: always_transmit}; + method idle = init_complete && event_handler_idle && event_counters.idle; endmodule -// -// Helpers -// +interface RegisterFile#(numeric type n, type t); + method Action select(ControllerId#(n) id); + method Action _write(t value); + method t _read(); +endinterface -instance Connectable#(Transceiver, Controller); - module mkConnection #(Transceiver txr, Controller c) (Empty); - mkConnection(txr, c.txr); - endmodule -endinstance +typedef RegisterFile#(n, Vector#(2, t)) + BufferedValueRegisterFile#(numeric type n, type t); -// Interrupts -Interrupts interrupts_none = unpack('0); +module mkBRAMRegisterFile (RegisterFile#(n, t)) + provisos (Bits#(t, t_sz)); + BRAM_PORT#(ControllerId#(n), t) ram <- mkBRAMCore1(valueof(n), False); + Reg#(RegisterFileRequest#(n, t)) request <- mkRegU(); -instance DefaultValue#(Interrupts); - defaultValue = interrupts_none; -endinstance + RWire#(ControllerId#(n)) controller_id <- mkRWire(); + RWire#(t) new_value <- mkRWire(); -instance Bitwise#(Interrupts); - function Interrupts \& (Interrupts i1, Interrupts i2) = - unpack(pack(i1) & pack(i2)); - function Interrupts \| (Interrupts i1, Interrupts i2) = - unpack(pack(i1) | pack(i2)); - function Interrupts \^ (Interrupts i1, Interrupts i2) = - unpack(pack(i1) ^ pack(i2)); - function Interrupts \~^ (Interrupts i1, Interrupts i2) = - unpack(pack(i1) ~^ pack(i2)); - function Interrupts \^~ (Interrupts i1, Interrupts i2) = - unpack(pack(i1) ^~ pack(i2)); - function Interrupts invert (Interrupts i) = - unpack(invert(pack(i))); - function Interrupts \<< (Interrupts i, t x) = - error("Left shift operation is not supported with type Interrupts"); - function Interrupts \>> (Interrupts i, t x) = - error("Right shift operation is not supported with type Interrupts"); - function Bit#(1) msb (Interrupts i) = - error("msb operation is not supported with type Interrupts"); - function Bit#(1) lsb (Interrupts i) = - error("lsb operation is not supported with type Interrupts"); -endinstance + (* fire_when_enabled *) + rule do_update_state; + request <= RegisterFileRequest { + id: fromMaybe(request.id, controller_id.wget), + data: new_value.wget}; + + // The register file contineously reads or writes the data for the set + // Controller id. If no new data is provided through `new_value`, the + // `data` field in the request automatically becomes `Invalid` causing a + // BRAM read instead of a write. + if (request.data matches tagged Valid .data) + ram.put(True, request.id, data); + else + ram.put(False, request.id, ?); + endrule -// Helpers used to map values/internal registers onto the register interface. -function ReadOnly#(t) valueToReadOnly(t val); - return ( - interface ReadOnly - method _read = val; - endinterface); -endfunction + method select = controller_id.wset; + method _read = ram.read; + method _write = new_value.wset; +endmodule -function ReadOnly#(v) castToReadOnly(t val) - provisos ( - Bits#(t, t_sz), - Bits#(v, v_sz), - Add#(t_sz, _, v_sz)); - return ( - interface ReadOnly - method _read = unpack(zeroExtend(pack(val))); - endinterface); -endfunction +typedef struct { + ControllerId#(n) id; + Maybe#(t) data; +} RegisterFileRequest#(numeric type n, type t) deriving (Bits, Eq, FShow); + +typedef struct { + ControllerId#(n) id; + union tagged { + TransmitterOutputEnableMode SetTransmitterOutputEnableMode; + SystemPowerRequest SystemPowerRequest; + } ev; +} SoftwareEvent#(numeric type n) deriving (Bits, Eq, FShow); + +typedef struct { + UInt#(1) current_status_message; + UInt#(6) status_message_timeout_ticks_remaining; + Bool present; + Vector#(3, Bool) history; +} PresenceRegister deriving (Bits, FShow); + +typedef struct { + UInt#(8) transmitter_output_disable_timeout_ticks_remaining; + TransmitterOutputEnableMode transmitter_output_enable_mode; + Bool transmitter_output_enabled; + LinkStatus receiver_status; +} TransceiverRegister deriving (Bits, FShow); + +typedef struct { + UInt#(6) ticks_remaining; +} HelloTimerRegister deriving (Bits, FShow); + +typedef enum { + AwaitingEvent = 0, + AwaitingControllerDataValid, + HandlingSoftwareRequest, + HandlingTickEvent, + HandlingReceiverEvent +} EventHandlerState deriving (Bits, Eq, FShow); + +module mkDefaultControllerUsingBRAM (Controller#(36)); + (* hide *) Controller#(36) _c <- mkController(defaultValue, False); + return _c; +endmodule -function ReadOnly#(v) castToReadOnlyIf(Bool pred, t val, t alt) - provisos ( - Bits#(t, t_sz), - Bits#(v, v_sz), - Add#(t_sz, _, v_sz)); - return castToReadOnly(pred ? val : alt); +function Stmt read_controller_register_into( + Controller#(n) controller, + ControllerId#(n) controller_id, + RegisterId register_id, + Reg#(register_value_type) destination) + provisos ( + Bits#(register_value_type, register_value_type_sz), + Add#(register_value_type_sz, a__, 8)); + return seq + controller.registers.request.put( + RegisterRequest { + op: tagged Read, + id: controller_id, + register: register_id}); + + action + let response <- controller.registers.response.get; + destination <= unpack(truncate(response)); + endaction + endseq; endfunction -function ReadVolatile#(IgnitionControllerRegisters::Counter) - readCounter(ActionValue#(Count) c) = - (interface ReadVolatile#(IgnitionControllerRegisters::Counter); - method ActionValue#(IgnitionControllerRegisters::Counter) _read(); - let x <- c; - return unpack(pack(x)); - endmethod - endinterface); - -function LinkEventCounterRegisters - asLinkEventCounterRegisters(CountingMonitor monitor) = - (interface LinkEventCounterRegisters; - interface Reg summary; - method _read = unpack(extend(pack(monitor.counters.summary))); - method Action _write(IgnitionControllerRegisters::LinkEvents e) = - monitor.counters.clear(unpack(truncate(pack(e)))); - endinterface - interface ReadVolatile encoding_error = readCounter(monitor.counters.encoding_error); - interface ReadVolatile decoding_error = readCounter(monitor.counters.decoding_error); - interface ReadVolatile ordered_set_invalid = readCounter(monitor.counters.ordered_set_invalid); - interface ReadVolatile message_version_invalid = readCounter(monitor.counters.message_version_invalid); - interface ReadVolatile message_type_invalid = readCounter(monitor.counters.message_type_invalid); - interface ReadVolatile message_checksum_invalid = readCounter(monitor.counters.message_checksum_invalid); - endinterface); - -function Registers registers(Controller c) = c.registers; - -function Vector#(n, Registers) register_pages(Vector#(n, Controller) controllers) = - map(registers, controllers); - -function TransceiverClient transceiver_client(Controller c) = c.txr; - -function Bool tx_enabled(Controller c) = c.status.always_transmit || c.status.target_present; +function Stmt clear_controller_counter( + Controller#(n) controller, + ControllerId#(n) controller_id, + CounterId counter_id) = + seq + controller.counters.request.put( + CounterAddress { + controller: controller_id, + counter: counter_id}); + + action + let count <- controller.counters.response.get; + endaction + endseq; + +function Stmt read_controller_counter_into( + Controller#(n) controller, + ControllerId#(n) controller_id, + CounterId counter_id, + Reg#(UInt#(8)) counter) = + seq + controller.counters.request.put( + CounterAddress { + controller: controller_id, + counter: counter_id}); + action + let count <- controller.counters.response.get; + counter <= count; + endaction + endseq; + +function Bit#(8) transceiver_state_value( + TransmitterOutputEnableMode mode, + Bool transmitter_status, + LinkStatus receiver_status) = + {2'h0, pack(mode), pack(transmitter_status), pack(receiver_status)}; endpackage diff --git a/hdl/ip/bsv/ignition/IgnitionControllerCounters.bsv b/hdl/ip/bsv/ignition/IgnitionControllerCounters.bsv new file mode 100644 index 00000000..55de0aa9 --- /dev/null +++ b/hdl/ip/bsv/ignition/IgnitionControllerCounters.bsv @@ -0,0 +1,611 @@ +package IgnitionControllerCounters; + +export ControllerCounters(..); +export CounterAddress(..); +export CounterId(..); +export Counter(..); +export CounterServer(..); +export CountableApplicationEvents(..); +export CountableTransceiverEvents(..); + +export mkControllerCounters; +export determine_transceiver_events; +export countable_transceiver_events_receiver_reset; + +import BRAMFIFO::*; +import BuildVector::*; +import ClientServer::*; +import FIFO::*; +import FIFOF::*; +import GetPut::*; +import Vector::*; + +import CounterRAM::*; + +import IgnitionControllerShared::*; +import IgnitionProtocol::*; + +typedef enum { + TargetPresent, + TargetTimeout, + StatusReceived, + StatusTimeout, + HelloSent, + SystemPowerRequestSent, + ControllerReceiverReset, + ControllerReceiverAligned, + ControllerReceiverLocked, + ControllerReceiverPolarityInverted, + ControllerEncodingError, + ControllerDecodingError, + ControllerOrderedSetInvalid, + ControllerMessageVersionInvalid, + ControllerMessageTypeInvalid, + ControllerMessageChecksumInvalid, + TargetLink0ReceiverReset, + TargetLink0ReceiverAligned, + TargetLink0ReceiverLocked, + TargetLink0ReceiverPolarityInverted, + TargetLink0EncodingError, + TargetLink0DecodingError, + TargetLink0OrderedSetInvalid, + TargetLink0MessageVersionInvalid, + TargetLink0MessageTypeInvalid, + TargetLink0MessageChecksumInvalid, + TargetLink1ReceiverReset, + TargetLink1ReceiverAligned, + TargetLink1ReceiverLocked, + TargetLink1ReceiverPolarityInverted, + TargetLink1EncodingError, + TargetLink1DecodingError, + TargetLink1OrderedSetInvalid, + TargetLink1MessageVersionInvalid, + TargetLink1MessageTypeInvalid, + TargetLink1MessageChecksumInvalid +} CounterId deriving (Bits, Eq, FShow); + +typedef struct { + ControllerId#(n) controller; + CounterId counter; +} CounterAddress#(numeric type n) deriving (Bits, Eq, FShow); + +typedef UInt#(8) Counter; +typedef Server#(CounterAddress#(n), Counter) CounterServer#(numeric type n); + +interface ControllerCounters#(numeric type n); + interface CounterServer#(n) counters; + + method Action count_application_events( + ControllerId#(n) controller, + CountableApplicationEvents events); + method Action count_transceiver_events( + ControllerId#(n) controller, + CountableTransceiverEvents events); + method Action count_target_link0_events( + ControllerId#(n) controller, + CountableTransceiverEvents events); + method Action count_target_link1_events( + ControllerId#(n) controller, + CountableTransceiverEvents events); + + // Flag indicating whether or not any counters are to be incremented. + (* always_ready *) method Bool idle(); +endinterface + +module mkControllerCounters (ControllerCounters#(n)); + // + // While processing software requests, tick- and receiver events the + // Controller observes the occurance of countable events (apologies for + // overloading the term "event" here). Examples include decoding or parse + // errors observed by the receiver, the number of times a Target has become + // present or timed out, the number of messages sent by the Controller, etc. + // Counters associated with these events are stored per controller using a + // BRAM (see `event_counters` below). These counters can be incremented one + // at the time using a producer port and read by software using a consumer + // port. + // + // A single event processed by the Controller may require several counters + // to be incremented. Since the event hander is expected to process events + // at a fixed three-cycle rate and it only needs to increment any counter by + // one, instead of modifying the counters directly handler emits bit vectors + // (stored in the `countable_*_events` FIFOs below) indicating which + // counters need to be incremented. + // + // While the Controller has already completed the event, these countable + // events vectors are then sequentially decoded into a specific counter + // address and merged into a single BRAM backed FIFO (see + // `increment_event_counter`). Counter addresses from this FIFO are then + // processed one at the time, incrementing each counter using the producer + // port of the counters BRAM. + // + // Decoding from the countable event vectors to individual counter addresses + // is done somewhat in parallel but merged at one counter/cycle into the + // final BRAM based FIFO. Bursts of vectors can be absorbed, but in order to + // guarantee not blocking the event handler the countable_*_events FIFOs use + // an unguarded enq side, allowing the Controller event handler to drop + // previous vectors if they are not drained quickly enough avoiding a + // handler stall. This will cause the counters to be undercounted, but the + // overall Controller state will remain consistent. + // + // The counters in BRAM are saturating and will natarally undercount if + // software is not collecting them in time, which makes event vectors being + // dropped under a high workload an acceptable decision. The counters + // therefor have "at least n observed occurances" semantics. + // + FIFOF#(CountableApplicationEventsWithId#(n)) + countable_application_events <- mkGFIFOF(True, False); + FIFOF#(CountableTransceiverEventsWithId#(n)) + countable_transceiver_events <- mkGFIFOF(True, False); + FIFOF#(CountableTransceiverEventsWithId#(n)) + countable_target_link0_events <- mkGFIFOF(True, False); + FIFOF#(CountableTransceiverEventsWithId#(n)) + countable_target_link1_events <- mkGFIFOF(True, False); + + FIFOF#(CounterAddress#(n)) + increment_application_event_counter <- mkGLFIFOF(False, True); + FIFOF#(CounterAddress#(n)) + increment_transceiver_event_counter <- mkGLFIFOF(False, True); + FIFOF#(CounterAddress#(n)) + increment_target_link0_event_counter <- mkGLFIFOF(False, True); + FIFOF#(CounterAddress#(n)) + increment_target_link1_event_counter <- mkGLFIFOF(False, True); + FIFOF#(CounterAddress#(n)) + increment_event_counter <- mkSizedBRAMFIFOF(255); + + // Saturating 8 bit counters in BRAM, incremented by 1 per request. + let n_counters = valueOf(TExp#(SizeOf#(CounterAddress#(n)))); + CounterRAM#(CounterAddress#(n), 8, 1) + event_counters <- mkCounterRAM(n_counters); + + // + // Event counters rules + // + + Reg#(ControllerId#(n)) countable_application_events_id <- mkRegU(); + Reg#(CountableApplicationEvents) + countable_application_events_remaining <- mkReg(0); + + (* fire_when_enabled *) + rule do_deq_countable_application_events + (countable_application_events_remaining == 0); + countable_application_events_id <= + countable_application_events.first.controller; + countable_application_events_remaining <= + countable_application_events.first.events; + + countable_application_events.deq(); + endrule + + (* fire_when_enabled *) + rule do_decode_countable_application_events + (countable_application_events_remaining != 0); + Reg#(CountableApplicationEvents) events = + countable_application_events_remaining; + + function increment(counter) = + increment_application_event_counter.enq( + CounterAddress { + controller: countable_application_events_id, + counter: counter}); + + if (events.status_received) begin + events.status_received <= False; + increment(StatusReceived); + end + else if (events.status_timeout) begin + events.status_timeout <= False; + increment(StatusTimeout); + end + else if (events.target_present) begin + events.target_present <= False; + increment(TargetPresent); + end + else if (events.target_timeout) begin + events.target_timeout <= False; + increment(TargetTimeout); + end + else if (events.hello_sent) begin + events.hello_sent <= False; + increment(HelloSent); + end + else if (events.system_power_request_sent) begin + events.system_power_request_sent <= False; + increment(SystemPowerRequestSent); + end + endrule + + Reg#(ControllerId#(n)) countable_transceiver_events_id <- mkRegU(); + Reg#(CountableTransceiverEvents) + countable_transceiver_events_remaining <- mkReg(0); + + (* fire_when_enabled *) + rule do_deq_countable_transceiver_events + (countable_transceiver_events_remaining == 0); + countable_transceiver_events_id <= + countable_transceiver_events.first.controller; + countable_transceiver_events_remaining <= + countable_transceiver_events.first.events; + + countable_transceiver_events.deq(); + endrule + + (* fire_when_enabled *) + rule do_decode_countable_transceiver_events + (countable_transceiver_events_remaining != 0); + Reg#(CountableTransceiverEvents) events = + countable_transceiver_events_remaining; + + function increment(counter) = + increment_transceiver_event_counter.enq( + CounterAddress { + controller: countable_transceiver_events_id, + counter: counter}); + + if (events.encoding_error) begin + events.encoding_error <= False; + increment(ControllerEncodingError); + end + else if (events.decoding_error) begin + events.decoding_error <= False; + increment(ControllerDecodingError); + end + else if (events.ordered_set_invalid) begin + events.ordered_set_invalid <= False; + increment(ControllerOrderedSetInvalid); + end + else if (events.message_version_invalid) begin + events.message_version_invalid <= False; + increment(ControllerMessageVersionInvalid); + end + else if (events.message_type_invalid) begin + events.message_type_invalid <= False; + increment(ControllerMessageTypeInvalid); + end + else if (events.message_checksum_invalid) begin + events.message_checksum_invalid <= False; + increment(ControllerMessageChecksumInvalid); + end + else if (events.aligned) begin + events.aligned <= False; + increment(ControllerReceiverAligned); + end + else if (events.locked) begin + events.locked <= False; + increment(ControllerReceiverLocked); + end + else if (events.polarity_inverted) begin + events.polarity_inverted <= False; + increment(ControllerReceiverPolarityInverted); + end + else if (events.reset) begin + events.reset <= False; + increment(ControllerReceiverReset); + end + endrule + + Reg#(ControllerId#(n)) countable_target_link0_events_id <- mkRegU(); + Reg#(CountableTransceiverEvents) + countable_target_link0_events_remaining <- mkReg(0); + + (* fire_when_enabled *) + rule do_deq_countable_link0_events + (countable_target_link0_events_remaining == 0); + countable_target_link0_events_id <= + countable_target_link0_events.first.controller; + countable_target_link0_events_remaining <= + countable_target_link0_events.first.events; + + countable_target_link0_events.deq(); + endrule + + (* fire_when_enabled *) + rule do_decode_countable_target_link0_events + (countable_target_link0_events_remaining != 0); + Reg#(CountableTransceiverEvents) events = + countable_target_link0_events_remaining; + + function increment(counter) = + increment_target_link0_event_counter.enq( + CounterAddress { + controller: countable_target_link0_events_id, + counter: counter}); + + if (events.encoding_error) begin + events.encoding_error <= False; + increment(TargetLink0EncodingError); + end + else if (events.decoding_error) begin + events.decoding_error <= False; + increment(TargetLink0DecodingError); + end + else if (events.ordered_set_invalid) begin + events.ordered_set_invalid <= False; + increment(TargetLink0OrderedSetInvalid); + end + else if (events.message_version_invalid) begin + events.message_version_invalid <= False; + increment(TargetLink0MessageVersionInvalid); + end + else if (events.message_type_invalid) begin + events.message_type_invalid <= False; + increment(TargetLink0MessageTypeInvalid); + end + else if (events.message_checksum_invalid) begin + events.message_checksum_invalid <= False; + increment(TargetLink0MessageChecksumInvalid); + end + else if (events.aligned) begin + events.aligned <= False; + increment(TargetLink0ReceiverAligned); + end + else if (events.locked) begin + events.locked <= False; + increment(TargetLink0ReceiverLocked); + end + else if (events.polarity_inverted) begin + events.polarity_inverted <= False; + increment(TargetLink0ReceiverPolarityInverted); + end + else if (events.reset) begin + events.reset <= False; + increment(TargetLink0ReceiverReset); + end + endrule + + Reg#(ControllerId#(n)) countable_target_link1_events_id <- mkRegU(); + Reg#(CountableTransceiverEvents) + countable_target_link1_events_remaining <- mkReg(0); + + (* fire_when_enabled *) + rule do_deq_countable_link1_events + (countable_target_link1_events_remaining == 0); + countable_target_link1_events_id <= + countable_target_link1_events.first.controller; + countable_target_link1_events_remaining <= + countable_target_link1_events.first.events; + + countable_target_link1_events.deq(); + endrule + + (* fire_when_enabled *) + rule do_decode_countable_target_link1_events + (countable_target_link1_events_remaining != 0); + Reg#(CountableTransceiverEvents) events = + countable_target_link1_events_remaining; + + function increment(counter) = + increment_target_link1_event_counter.enq( + CounterAddress { + controller: countable_target_link1_events_id, + counter: counter}); + + if (events.encoding_error) begin + events.encoding_error <= False; + increment(TargetLink1EncodingError); + end + else if (events.decoding_error) begin + events.decoding_error <= False; + increment(TargetLink1DecodingError); + end + else if (events.ordered_set_invalid) begin + events.ordered_set_invalid <= False; + increment(TargetLink1OrderedSetInvalid); + end + else if (events.message_version_invalid) begin + events.message_version_invalid <= False; + increment(TargetLink1MessageVersionInvalid); + end + else if (events.message_type_invalid) begin + events.message_type_invalid <= False; + increment(TargetLink1MessageTypeInvalid); + end + else if (events.message_checksum_invalid) begin + events.message_checksum_invalid <= False; + increment(TargetLink1MessageChecksumInvalid); + end + else if (events.aligned) begin + events.aligned <= False; + increment(TargetLink1ReceiverAligned); + end + else if (events.locked) begin + events.locked <= False; + increment(TargetLink1ReceiverLocked); + end + else if (events.polarity_inverted) begin + events.polarity_inverted <= False; + increment(TargetLink1ReceiverPolarityInverted); + end + else if (events.reset) begin + events.reset <= False; + increment(TargetLink1ReceiverReset); + end + endrule + + Reg#(Vector#(4, Bool)) + increment_event_counter_source_select_base <- mkReg(unpack(1)); + + (* fire_when_enabled *) + rule do_merge_increment_counter_requests; + let pending = vec( + increment_application_event_counter.notEmpty, + increment_transceiver_event_counter.notEmpty, + increment_target_link0_event_counter.notEmpty, + increment_target_link1_event_counter.notEmpty); + + let source_select = + round_robin_select( + pending, + increment_event_counter_source_select_base); + + function Action deq_from(FIFOF#(CounterAddress#(n)) source) = + action + increment_event_counter.enq(source.first); + source.deq(); + increment_event_counter_source_select_base <= + rotateR(source_select); + endaction; + + if (source_select[0]) + deq_from(increment_application_event_counter); + else if (source_select[1]) + deq_from(increment_transceiver_event_counter); + else if (source_select[2]) + deq_from(increment_target_link0_event_counter); + else if (source_select[3]) + deq_from(increment_target_link1_event_counter); + endrule + + (* fire_when_enabled *) + rule do_increment_counter; + increment_event_counter.deq(); + event_counters.producer.request.put( + CounterWriteRequest { + id: increment_event_counter.first, + op: Add, + amount: 1}); + endrule + + method count_application_events(controller, events) = + countable_application_events.enq( + CountableEventsWithId { + controller: controller, + events: events}); + method count_transceiver_events(controller, events) = + countable_transceiver_events.enq( + CountableEventsWithId { + controller: controller, + events: events}); + method count_target_link0_events(controller, events) = + countable_target_link0_events.enq( + CountableEventsWithId { + controller: controller, + events: events}); + method count_target_link1_events(controller, events) = + countable_target_link1_events.enq( + CountableEventsWithId { + controller: controller, + events: events}); + + interface Server counters; + interface Put request; + method put(address) = + event_counters.consumer.request.put( + CounterReadRequest { + id: address, + clear: True}); + endinterface + + interface Get response = event_counters.consumer.response; + endinterface + + method idle = + !(countable_application_events.notEmpty || + countable_transceiver_events.notEmpty || + countable_target_link0_events.notEmpty || + countable_target_link1_events.notEmpty || + increment_application_event_counter.notEmpty || + increment_transceiver_event_counter.notEmpty || + increment_target_link0_event_counter.notEmpty || + increment_target_link1_event_counter.notEmpty || + increment_event_counter.notEmpty) && + countable_application_events_remaining == 0 && + countable_transceiver_events_remaining == 0 && + countable_target_link0_events_remaining == 0 && + countable_target_link1_events_remaining == 0 && + event_counters.producer.idle; +endmodule + +typedef struct { + Bool reset; + Bool polarity_inverted; + Bool locked; + Bool aligned; + Bool message_checksum_invalid; + Bool message_type_invalid; + Bool message_version_invalid; + Bool ordered_set_invalid; + Bool decoding_error; + Bool encoding_error; +} CountableTransceiverEvents deriving (Bits, Eq, FShow); + +instance Literal#(CountableTransceiverEvents); + function CountableTransceiverEvents fromInteger(Integer x) = + unpack(fromInteger(x)); + function Bool inLiteralRange(CountableTransceiverEvents e, Integer x) = + fromInteger(x) <= pack(CountableTransceiverEvents'(unpack('1))); +endinstance + +function CountableTransceiverEvents determine_transceiver_events( + LinkStatus past_status, + LinkStatus current_status, + LinkEvents link_events, + Bool infer_reset) = + CountableTransceiverEvents { + reset: + (infer_reset && + past_status.polarity_inverted && + !current_status.polarity_inverted) || + (infer_reset && + past_status.receiver_locked && + !current_status.receiver_locked) || + (infer_reset && + past_status.receiver_aligned && + !current_status.receiver_aligned), + polarity_inverted: + !past_status.polarity_inverted && + current_status.polarity_inverted, + locked: + !past_status.receiver_locked && + current_status.receiver_locked, + aligned: + !past_status.receiver_aligned && + current_status.receiver_aligned, + message_checksum_invalid: link_events.message_checksum_invalid, + message_type_invalid: link_events.message_type_invalid, + message_version_invalid: link_events.message_version_invalid, + ordered_set_invalid: link_events.ordered_set_invalid, + decoding_error: link_events.decoding_error, + encoding_error: link_events.encoding_error}; + +CountableTransceiverEvents countable_transceiver_events_receiver_reset = + CountableTransceiverEvents { + reset: True, + polarity_inverted: False, + locked: False, + aligned: False, + message_checksum_invalid: False, + message_type_invalid: False, + message_version_invalid: False, + ordered_set_invalid: False, + decoding_error: False, + encoding_error: False}; + +typedef struct { + Bool system_power_request_sent; + Bool hello_sent; + Bool target_timeout; + Bool target_present; + Bool status_timeout; + Bool status_received; +} CountableApplicationEvents deriving (Bits, Eq, FShow); + +instance Literal#(CountableApplicationEvents); + function CountableApplicationEvents fromInteger(Integer x) = + unpack(fromInteger(x)); + function Bool inLiteralRange(CountableApplicationEvents e, Integer x) = + fromInteger(x) <= pack(e); +endinstance + +typedef struct { + ControllerId#(n) controller; + events_type events; +} CountableEventsWithId#(numeric type n, type events_type) + deriving (Bits, FShow); + +typedef CountableEventsWithId#(n, CountableTransceiverEvents) + CountableTransceiverEventsWithId#(numeric type n); + +typedef CountableEventsWithId#(n, CountableApplicationEvents) + CountableApplicationEventsWithId#(numeric type n); + +endpackage diff --git a/hdl/ip/bsv/ignition/IgnitionControllerShared.bsv b/hdl/ip/bsv/ignition/IgnitionControllerShared.bsv new file mode 100644 index 00000000..e1a43f83 --- /dev/null +++ b/hdl/ip/bsv/ignition/IgnitionControllerShared.bsv @@ -0,0 +1,19 @@ +package IgnitionControllerShared; + +typedef UInt#(TLog#(n)) ControllerId#(numeric type n); + +// Given a bit_vector_t with zero or more bits indicating requests and a +// bit_vector_t with exactly one bit set indicating the preferred (next) request +// to select, round-robin select the next request. +function bit_vector_t round_robin_select( + bit_vector_t pending, + bit_vector_t base) + provisos (Bits#(bit_vector_t, sz)); + let _base = extend(pack(base)); + let _pending = {pack(pending), pack(pending)}; + + match {.left, .right} = split(_pending & ~(_pending - _base)); + return unpack(left | right); +endfunction + +endpackage diff --git a/hdl/ip/bsv/ignition/IgnitionEventCounter.bsv b/hdl/ip/bsv/ignition/IgnitionEventCounter.bsv deleted file mode 100644 index 31601e57..00000000 --- a/hdl/ip/bsv/ignition/IgnitionEventCounter.bsv +++ /dev/null @@ -1,118 +0,0 @@ -package IgnitionEventCounter; - -export Count(..); -export Counter(..); -export EventCounters(..); -export CountingMonitor(..); -export mkCounter; -export mkCountingMonitor; - -import IgnitionProtocol::*; - - -// -// Event counters. -// - -typedef struct { - UInt#(8) x; -} Count deriving (Bits, Literal, Eq, FShow); - -interface Counter; - method ActionValue#(Count) _read(); - method Action send(); - method Action clear(); - method Bool zero(); -endinterface - -module mkCounter (Counter); - Reg#(Count) count <- mkRegU(); - Reg#(Bool) zero_ <- mkRegU(); - - PulseWire clear_ <- mkPulseWire; - PulseWire read <- mkPulseWire(); - PulseWire increment <- mkPulseWire(); - - (* fire_when_enabled *) - rule do_update; - let x_ = (read || clear_) ? 0 : count.x; - count <= Count {x: satPlus(Sat_Bound, x_, (increment ? 1 : 0))}; - - if (read || clear_ || increment) - zero_ <= !increment; - endrule - - method ActionValue#(Count) _read(); - read.send(); - return count; - endmethod - - method send = increment.send; - method clear = clear_.send; - method zero = zero_; -endmodule - -interface EventCounters; - method LinkEvents summary(); - method ActionValue#(Count) encoding_error; - method ActionValue#(Count) decoding_error; - method ActionValue#(Count) ordered_set_invalid; - method ActionValue#(Count) message_version_invalid; - method ActionValue#(Count) message_type_invalid; - method ActionValue#(Count) message_checksum_invalid; - method Action clear(LinkEvents counters); -endinterface - -interface CountingMonitor; - method Action monitor(LinkEvents events); - interface EventCounters counters; -endinterface - -module mkCountingMonitor (CountingMonitor); - Counter n_encoding_error <- mkCounter(); - Counter n_decoding_error <- mkCounter(); - Counter n_ordered_set_invalid <- mkCounter(); - Counter n_message_version_invalid <- mkCounter(); - Counter n_message_type_invalid <- mkCounter(); - Counter n_message_checksum_invalid <- mkCounter(); - - method Action monitor(LinkEvents events); - if (events.encoding_error) n_encoding_error.send(); - if (events.decoding_error) n_decoding_error.send(); - if (events.ordered_set_invalid) n_ordered_set_invalid.send(); - if (events.message_version_invalid) - n_message_version_invalid.send(); - if (events.message_type_invalid) n_message_type_invalid.send(); - if (events.message_checksum_invalid) - n_message_checksum_invalid.send(); - endmethod - - interface EventCounters counters; - method summary = LinkEvents { - encoding_error: !n_encoding_error.zero, - decoding_error: !n_decoding_error.zero, - ordered_set_invalid: !n_ordered_set_invalid.zero, - message_version_invalid: !n_message_version_invalid.zero, - message_type_invalid: !n_message_type_invalid.zero, - message_checksum_invalid: !n_message_checksum_invalid.zero}; - method encoding_error = n_encoding_error; - method decoding_error = n_decoding_error; - method ordered_set_invalid = n_ordered_set_invalid; - method message_version_invalid = n_message_version_invalid; - method message_type_invalid = n_message_type_invalid; - method message_checksum_invalid = n_message_checksum_invalid; - - method Action clear(LinkEvents events); - if (events.encoding_error) n_encoding_error.clear(); - if (events.decoding_error) n_decoding_error.clear(); - if (events.ordered_set_invalid) n_ordered_set_invalid.clear(); - if (events.message_version_invalid) - n_message_version_invalid.clear(); - if (events.message_type_invalid) n_message_type_invalid.clear(); - if (events.message_checksum_invalid) - n_message_checksum_invalid.clear(); - endmethod - endinterface -endmodule - -endpackage diff --git a/hdl/ip/bsv/ignition/IgnitionProtocol.bsv b/hdl/ip/bsv/ignition/IgnitionProtocol.bsv index da329c09..b0060eee 100644 --- a/hdl/ip/bsv/ignition/IgnitionProtocol.bsv +++ b/hdl/ip/bsv/ignition/IgnitionProtocol.bsv @@ -75,8 +75,8 @@ typedef struct { typedef enum { SystemPowerOff = 1, SystemPowerOn = 2, - SystemReset = 3 -} Request deriving (Bits, Eq, FShow); + SystemPowerReset = 3 +} SystemPowerRequest deriving (Bits, Eq, FShow); typedef union tagged { struct { @@ -90,7 +90,7 @@ typedef union tagged { LinkEvents link1_events; } Status; void Hello; - Request Request; + SystemPowerRequest Request; } Message deriving (Bits, Eq, FShow); // The Target is only supposed to receive Hello and Request messages from a @@ -99,7 +99,7 @@ typedef union tagged { // implement a more size efficient parser/receiver for the Target. typedef union tagged { void Hello; - Request Request; + SystemPowerRequest Request; } ControllerMessage deriving (Bits, Eq, FShow); // @@ -144,6 +144,23 @@ function module#(CRC#(8)) mkIgnitionCRC() = crc_parameters.reflect_data, crc_parameters.reflect_remainder); +function Bit#(8) crc_add(Bit#(8) running_crc, Bit#(8) data); + Bit#(8) remainder = running_crc ^ data; + + for(Integer i = 0; i < 8; i = i + 1) begin + if (msb(remainder) == 1) + remainder = (remainder << 1) ^ crc_parameters.poly; + else + remainder = (remainder << 1); + end + + return remainder; +endfunction + +function Bit#(8) crc_result(Bit#(8) running_crc) = + running_crc ^ crc_parameters.final_xor; + + // // Encoding Ordered Sets // @@ -244,7 +261,9 @@ endinstance RequestStatus request_status_none = defaultValue; RequestStatus request_status_power_off_in_progress = unpack('h1); RequestStatus request_status_power_on_in_progress = unpack('h2); -RequestStatus request_status_reset_in_progress = unpack('h4); +RequestStatus request_status_power_reset_in_progress = unpack('h4); +RequestStatus request_status_power_reset_off_in_progress = unpack('h5); +RequestStatus request_status_power_reset_on_in_progress = unpack('h6); instance DefaultValue#(LinkEvents); defaultValue = unpack('0); @@ -276,10 +295,6 @@ LinkEvents link_events_message_version_invalid = unpack('h8); LinkEvents link_events_message_type_invalid = unpack('h10); LinkEvents link_events_message_checksum_invalid = unpack('h20); -instance DefaultValue#(LinkStatus); - defaultValue = unpack('0); -endinstance - instance Bitwise#(LinkStatus); function LinkStatus \& (LinkStatus s1, LinkStatus s2) = unpack(pack(s1) & pack(s2)); function LinkStatus \| (LinkStatus s1, LinkStatus s2) = unpack(pack(s1) | pack(s2)); @@ -298,9 +313,19 @@ instance Bitwise#(LinkStatus); error("lsb operation is not supported with type LinkStatus"); endinstance -LinkStatus link_status_disconnected = defaultValue; -LinkStatus link_status_connected = unpack('h3); -LinkStatus link_status_connected_polarity_inverted = unpack('h7); +LinkStatus link_status_none = unpack('0); +LinkStatus link_status_aligned = unpack('h1); +LinkStatus link_status_locked = unpack('h2); +LinkStatus link_status_polarity_inverted = unpack('h4); +LinkStatus link_status_disconnected = link_status_none; +LinkStatus link_status_connected = link_status_aligned | link_status_locked; +LinkStatus link_status_connected_polarity_inverted = + link_status_connected | + link_status_polarity_inverted; + +instance DefaultValue#(LinkStatus); + defaultValue = link_status_none; +endinstance // // Status Message pretty printer diff --git a/hdl/ip/bsv/ignition/IgnitionProtocolDeparser.bsv b/hdl/ip/bsv/ignition/IgnitionProtocolDeparser.bsv index dae2b54e..02b1d9c4 100644 --- a/hdl/ip/bsv/ignition/IgnitionProtocolDeparser.bsv +++ b/hdl/ip/bsv/ignition/IgnitionProtocolDeparser.bsv @@ -87,4 +87,35 @@ function Tuple2#(State, Value) tuple2(AwaitReset, end_of_message1); endcase; +function Tuple2#(State, Value) + deparse_controller_message(State s, ControllerMessage m, Bit#(8) crc) = + case (s) + EmitStartOfMessage: + tuple2(EmitVersion, start_of_message); + + EmitVersion: + tuple2( + EmitMessageType, + tagged D fromInteger(defaultValue.version)); + + EmitMessageType: + case (m) matches + tagged Hello: + tuple2(EmitChecksum, tagged D 2); + tagged Request .*: + tuple2(EmitRequest, tagged D 3); + endcase + + // Emit Request + EmitRequest: + tuple2(EmitChecksum, tagged D extend(pack(m.Request))); + + // Emit end of message + EmitChecksum: + tuple2(EmitEndOfMessage1, tagged D crc); + + EmitEndOfMessage1: + tuple2(AwaitReset, end_of_message1); + endcase; + endpackage diff --git a/hdl/ip/bsv/ignition/IgnitionProtocolParser.bsv b/hdl/ip/bsv/ignition/IgnitionProtocolParser.bsv index 3f6ba4e1..4b80e15e 100644 --- a/hdl/ip/bsv/ignition/IgnitionProtocolParser.bsv +++ b/hdl/ip/bsv/ignition/IgnitionProtocolParser.bsv @@ -12,6 +12,10 @@ export ControllerMessageParserState; export ControllerMessageParser; export mkControllerMessageParser; +export StreamingStatusMessageParserState(..); +export StreamingStatusMessageParser; +export mkStreamingStatusMessageParser; + import DefaultValue::*; import Encoding8b10b::*; @@ -117,7 +121,7 @@ module mkMessageParser (Parser#(MessageParserState, Message)); case (d) 1: tagged ComparingChecksum tagged Request SystemPowerOff; 2: tagged ComparingChecksum tagged Request SystemPowerOn; - 3: tagged ComparingChecksum tagged Request SystemReset; + 3: tagged ComparingChecksum tagged Request SystemPowerReset; default: tagged AwaitingReset tagged Error RequestInvalid; endcase @@ -325,7 +329,7 @@ module mkControllerMessageParser case (d) 1: tagged ComparingChecksum tagged Request SystemPowerOff; 2: tagged ComparingChecksum tagged Request SystemPowerOn; - 3: tagged ComparingChecksum tagged Request SystemReset; + 3: tagged ComparingChecksum tagged Request SystemPowerReset; default: tagged AwaitingReset tagged Error RequestInvalid; endcase @@ -377,4 +381,139 @@ module mkControllerMessageParser endmethod endmodule +typedef Parser#(StreamingStatusMessageParserState, void) + StreamingStatusMessageParser; + +typedef union tagged { + void AwaitingOrderedSet; + void ParsingIdle; + void ParsingVersion; + void ParsingMessageType; + void ParsingSystemType; + SystemType ParsingSystemStatus; + SystemStatus ParsingSystemFaults; + SystemFaults ParsingRequestStatus; + RequestStatus ParsingLink0Status; + LinkStatus ParsingLink0Events; + LinkEvents ParsingLink1Status; + LinkStatus ParsingLink1Events; + LinkEvents ComparingChecksum; + void ParsingEndOfMessage1; + Result#(void) AwaitingReset; +} StreamingStatusMessageParserState deriving (Bits, Eq, FShow); + +instance DefaultValue#(StreamingStatusMessageParserState); + defaultValue = tagged AwaitingOrderedSet; +endinstance + +module mkStreamingStatusMessageParser + (Parser#(StreamingStatusMessageParserState, void)); + + method ActionValue#(StreamingStatusMessageParserState) parse( + StreamingStatusMessageParserState state, + Value value, + Bit#(8) running_checksum); + return case (tuple2(state, value)) matches + {tagged AwaitingOrderedSet, tagged K 'hbc}: // K28.5, comma + tagged ParsingIdle; + {tagged AwaitingOrderedSet, tagged K 'h1c}: // K28.0, start_of_message + tagged ParsingVersion; + {tagged AwaitingOrderedSet, .*}: + tagged AwaitingOrderedSet; + + // Parse idle sets + {tagged ParsingIdle, tagged D 'h4a}: // D10.2, idle1 + tagged AwaitingReset tagged Idle1 False; + {tagged ParsingIdle, tagged D 'hb5}: // D21.5, bit inverse of idle1 + tagged AwaitingReset tagged Idle1 True; + {tagged ParsingIdle, tagged D 'hb3}: // D19.5, idle2 + tagged AwaitingReset tagged Idle2 False; + {tagged ParsingIdle, tagged D 'h4c}: // D12.2, bit inverse of idle2 + tagged AwaitingReset tagged Idle2 True; + + // Parse message header + {tagged ParsingVersion, tagged D 1}: + tagged ParsingMessageType; + {tagged ParsingVersion, tagged D .*}: + tagged AwaitingReset tagged Error VersionInvalid; + + {tagged ParsingMessageType, tagged D .d}: + case (d) + 1: tagged ParsingSystemType; + default: tagged AwaitingReset tagged Error MessageTypeInvalid; + endcase + + // Parse Status + {tagged ParsingSystemType, tagged D .d}: + tagged ParsingSystemStatus unpack(truncate(d)); + + {tagged ParsingSystemStatus .*, tagged D .d}: + tagged ParsingSystemFaults unpack(truncate(d)); + + {tagged ParsingSystemFaults .*, tagged D .d}: + tagged ParsingRequestStatus unpack(truncate(d)); + + {tagged ParsingRequestStatus .*, tagged D .d}: + tagged ParsingLink0Status unpack(truncate(d)); + + {tagged ParsingLink0Status .*, tagged D .d}: + tagged ParsingLink0Events unpack(truncate(d)); + + {tagged ParsingLink0Events .*, tagged D .d}: + tagged ParsingLink1Status unpack(truncate(d)); + + {tagged ParsingLink1Status .*, tagged D .d}: + tagged ParsingLink1Events unpack(truncate(d)); + + {tagged ParsingLink1Events .*, tagged D .d}: + tagged ComparingChecksum unpack(truncate(d)); + + // Parse message footer + {tagged ComparingChecksum .*, tagged D .d}: + (begin + if (d == running_checksum) + tagged ParsingEndOfMessage1; + else + tagged AwaitingReset tagged Error ChecksumInvalid; + end); + + {tagged ParsingEndOfMessage1, tagged K 'hf7}: // K23.7 + tagged AwaitingReset tagged Message (?); + + // Reject anything else as an invalid ordered set. + default: + tagged AwaitingReset tagged Error OrderedSetInvalid; + endcase; + endmethod + + method Bool awaiting_ordered_set(StreamingStatusMessageParserState state); + return case (state) matches + tagged AwaitingOrderedSet: True; + default: False; + endcase; + endmethod + + method Bool parsing_idle(StreamingStatusMessageParserState state); + return case (state) matches + tagged ParsingIdle: True; + default: False; + endcase; + endmethod + + method Bool done(StreamingStatusMessageParserState state); + return case (state) matches + tagged AwaitingReset .*: True; + default: False; + endcase; + endmethod + + method Maybe#(Result#(void)) + result(StreamingStatusMessageParserState state); + return case (state) matches + tagged AwaitingReset .result: tagged Valid result; + default: tagged Invalid; + endcase; + endmethod +endmodule + endpackage diff --git a/hdl/ip/bsv/ignition/IgnitionReceiver.bsv b/hdl/ip/bsv/ignition/IgnitionReceiver.bsv index 63428a82..e4fe6edf 100644 --- a/hdl/ip/bsv/ignition/IgnitionReceiver.bsv +++ b/hdl/ip/bsv/ignition/IgnitionReceiver.bsv @@ -3,11 +3,30 @@ package IgnitionReceiver; export Receiver(..); export mkReceiver; +export ControllerReceiver(..); +export mkControllerReceiver; +export mkControllerReceiver36; +export mkDeserializerEventMux1; +export mkDeserializerEventMux2; +export mkDeserializerEventMux4; +export mkDeserializerEventMux36; +export mkDeserializerEventMux36Alt; + +export StatusMessageFragment(..); +export DeserializerEvent(..); +export DeserializerEvent_$ev(..); +export DeserializerEventFIFOs(..); +export ReceiverEvent(..); +export ReceiverEvent_$ev(..); + +import BRAMCore::*; +import BRAMFIFO::*; import ConfigReg::*; import Connectable::*; import DefaultValue::*; import DReg::*; import FIFO::*; +import FIFOF::*; import GetPut::*; import OInt::*; import Vector::*; @@ -26,6 +45,7 @@ interface Receiver#(numeric type n, type message_t); method Vector#(n, LinkStatus) status(); method Vector#(n, LinkEvents) events(); method Vector#(n, Bool) locked_timeout(); + method Vector#(n, Bool) reset_event(); method Action tick_1khz(); endinterface @@ -68,6 +88,7 @@ typedef struct { PulseWire character_accepted; // Watchdog interface for the channel. Reg#(Bool) locked_timeout; + Reg#(Bool) reset_event; } State#(type parser_t); module mkReceiver @@ -106,6 +127,8 @@ module mkReceiver channels[i].character <- mkWire(); channels[i].character_accepted <- mkPulseWire(); + + channels[i].reset_event <- mkDReg(False); end Reg#(UInt#(TLog#(n))) reset_or_fetch_select <- mkReg(0); @@ -162,6 +185,8 @@ module mkReceiver channels[i].parser_state <= defaultValue; channels[i].expect_idle <= False; channels[i].idle_set_valid_history <= replicate(unknown); + + channels[i].reset_event <= True; endrule // Fetch the next character for the channel under consideration. Note @@ -193,7 +218,7 @@ module mkReceiver endrule // Because the rule above may block if no character is received from the - // deserializer the, a `locked_timeout` can preempt this and return the + // deserializer `locked_timeout` can preempt this and return the // receiver to the `Resetting` phase. This guarantees that the receiver // is either locked or periodically returns to the `Resetting` phase. // Upon receiver reset the deserializer will slip bits until the next @@ -378,13 +403,13 @@ module mkReceiver channels[i].locked <= True; end - // Discard the shared parse state, allowing the next channel to + // Discard the shared parser state, allowing the next channel to // continue receiving a character. decode_result.deq(); // Based on the outcome of the character valid history and idle set - // checks either reset the receiver or continue waiting for the next - // character. + // checks, either reset the receiver or continue waiting for the + // next character. channels[i].phase <= reset_receiver ? Resetting : Fetching; endrule @@ -462,6 +487,7 @@ module mkReceiver method status = map(receiver_status, channels); method events = map(receiver_events, channels); method locked_timeout = map(receiver_locked_timeout, channels); + method reset_event = map(receiver_reset_event, channels); method Action tick_1khz(); // This automatically rolls over from 0, restarting the watchdog @@ -535,6 +561,9 @@ endfunction function Bool receiver_locked_timeout(State#(parser_t) channel) = channel.locked_timeout; +function Bool receiver_reset_event(State#(parser_t) channel) = + channel.reset_event; + instance Connectable#(Vector#(n, Deserializer8b10b::Deserializer), Receiver#(n, message_t)); module mkConnection #( Vector#(n, Deserializer8b10b::Deserializer) des, @@ -556,4 +585,736 @@ instance Connectable#(Vector#(n, Deserializer8b10b::Deserializer), Receiver#(n, endmodule endinstance +// +// Controller Receiver +// + +interface ControllerReceiver #(numeric type n); + interface Vector#(n, DeserializerClient) rx; + interface Get#(ReceiverEvent#(n)) events; + method Action tick_1khz(); +endinterface + +interface DeserializerChannel #(numeric type n); + interface DeserializerClient deserializer; + interface FIFOF#(DeserializerEvent#(n)) events; + + // Control methods used by upstream receiver logic. + method Action request_reset(); + method Action set_search_for_comma(Bool search_for_comma); + method Action set_invert_polarity(Bool invert_polarity); +endinterface + +module mkDeserializerChannel #(Integer id) (DeserializerChannel#(n)); + RWire#(DeserializedCharacter) next_character <- mkRWire(); + RWire#(DeserializerEvent#(n)) next_event <- mkRWire(); + + PulseWire deq <- mkPulseWire(); + PulseWire reset_request <- mkPulseWire(); + + Reg#(Bool) reset_requested <- mkReg(True); + Reg#(Bool) search_for_comma <- mkReg(False); + Reg#(Bool) invert_polarity <- mkReg(False); + + (* fire_when_enabled *) + rule do_enq_reset_event (reset_requested); + next_event.wset(DeserializerEvent { + id: fromInteger(id), + ev: tagged DecoderReset}); + endrule + + (* fire_when_enabled *) + rule do_enq_character ( + !reset_requested &&& + next_character.wget matches tagged Valid .c); + next_event.wset(DeserializerEvent { + id: fromInteger(id), + ev: tagged Character c.c}); + endrule + + (* fire_when_enabled *) + rule ack_reset_request (reset_requested && deq); + reset_requested <= reset_request; + endrule + + (* fire_when_enabled *) + rule do_request_reset (!reset_requested); + reset_requested <= reset_request; + endrule + + interface DeserializerClient deserializer; + interface PutS character; + method offer = next_character.wset; + method accepted = deq; + endinterface + + method search_for_comma = search_for_comma; + method invert_polarity = invert_polarity; + endinterface + + interface FIFOF events; + method Action enq(DeserializerEvent#(n) e); + endmethod + method deq = deq.send; + method first = fromMaybe(?, next_event.wget); + method notEmpty = isValid(next_event.wget); + method notFull = !isValid(next_event.wget); + method Action clear() = noAction; + endinterface + + method request_reset = reset_request.send; + method set_search_for_comma = search_for_comma._write; + method set_invert_polarity = invert_polarity._write; +endmodule + +typedef Vector#(m, FIFOF#(DeserializerEvent#(n))) + DeserializerEventFIFOs#(numeric type m, numeric type n); + +module mkControllerReceiver #( + function module#(Empty) mkDeserializerEventMux( + Vector#(n, FIFOF#(DeserializerEvent#(n))) sources, + FIFOF#(DeserializerEvent#(n)) sink)) + (ControllerReceiver#(n)) + provisos ( + NumAlias#(TLog#(n), id_sz)); + Vector#(n, DeserializerChannel#(n)) + channels <- mapM(mkDeserializerChannel, genVector); + Vector#(n, Reg#(Bool)) channels_locked <- replicateM(mkReg(False)); + + Reg#(UInt#(9)) watchdog_ticks_remaining <- mkRegU(); + Reg#(Bool) watchdog_fired <- mkDReg(False); + + // Use a regular FIFO here but of size two, allowing it to act as a skid + // buffer. This decouples the demux pipeline from the receiver logic, + // reducing the length of pipeline enable signals while preserving the + // ability to process a receiver event very cycle. + FIFOF#(DeserializerEvent#(n)) deserializer_events_l0 <- mkGFIFOF(False, True); + + mkDeserializerEventMux(map(events, channels), deserializer_events_l0); + + // + // Receive Phase + // + + BRAM_DUAL_PORT#(UInt#(id_sz), ReceiveState1) + receive_state_1 <- mkBRAMCore2(valueof(n), False); + BRAM_DUAL_PORT#(UInt#(id_sz), ReceiveState2) + receive_state_2 <- mkBRAMCore2(valueof(n), False); + + StreamingStatusMessageParser parser <- mkStreamingStatusMessageParser(); + + Reg#(Maybe#(DeserializerEvent#(n))) deserializer_event <- mkReg(tagged Invalid); + Reg#(Maybe#(DecoderEvent#(n))) decoder_event <- mkReg(tagged Invalid); + Reg#(Maybe#(ParserEvent#(n))) parser_event <- mkReg(tagged Invalid); + FIFOF#(ReceiverEvent#(n)) receiver_events <- mkSizedBRAMFIFOF(1023); + + Reg#(Maybe#(UInt#(id_sz))) receive_state_1_id <- mkReg(tagged Invalid); + Reg#(ReceiveState1) receive_state_1_next <- mkRegU(); + Reg#(Maybe#(UInt#(id_sz))) receive_state_2_id <- mkReg(tagged Invalid); + Reg#(ReceiveState2) receive_state_2_next <- mkRegU(); + + function Bool stage_available(Maybe#(t) next_stage) = + !isValid(next_stage) || receiver_events.notFull; + + (* fire_when_enabled *) + rule do_handle_events; + // + // Wait for BRAM read. + // + + if (deserializer_events_l0.notEmpty && + stage_available(deserializer_event)) begin + receive_state_1.a.put(False, deserializer_events_l0.first.id, ?); + deserializer_event <= tagged Valid deserializer_events_l0.first; + deserializer_events_l0.deq(); + end + else if (!deserializer_events_l0.notEmpty && + receiver_events.notFull) begin + deserializer_event <= tagged Invalid; + end + + // + // Decode. + // + + if (deserializer_event matches tagged Valid .ev &&& + stage_available(decoder_event)) begin + let rd = receive_state_1.a.read.rd; + let parser_state = receive_state_1.a.read.parser_state; + let running_crc = receive_state_1.a.read.running_crc; + + case (ev.ev) matches + tagged DecoderReset: + decoder_event <= tagged Valid + DecoderEvent { + id: ev.id, + ev: tagged ParserReset}; + + tagged Character .c: + decoder_event <= tagged Valid + DecoderEvent { + id: ev.id, + ev: tagged DecodeResult { + result: decode(c, rd), + parser_state: parser_state, + running_crc: running_crc}}; + endcase + end + else if (deserializer_event matches tagged Invalid &&& + receiver_events.notFull) begin + decoder_event <= tagged Invalid; + end + + // + // Parse. + // + + if (decoder_event matches tagged Valid .ev &&& + stage_available(parser_event)) begin + case (ev.ev) matches + tagged ParserReset: + parser_event <= tagged Valid + ParserEvent { + id: ev.id, + ev: tagged ReceiverReset}; + + tagged DecodeResult { + result: .decode_result, + parser_state: .parser_state, + running_crc: .running_crc}: begin + // Determine the value to be parsed based on the decoder + // result. + match {.value, .character_valid} = + case (decode_result.value) matches + tagged Valid .value: tuple2(value, True); + tagged Invalid .*: + tuple2(end_of_message_invalid, False); + endcase; + + // if (ev.id == 0) begin + // $display("%05t: ", $time, fshow(parser_state), " ", fshow(value)); + // end + + // Parse the value given the current parser state. If the + // case statement above returned the + // `end_of_message_invalid` value it'll force a parse + // failure causing the parser to get reset in the next step. + // + // The next parser state may hold a parse Error, idle token + // or a byte from the incoming message for the receive stage + // to use. + let parser_state_next <- + parser.parse( + parser_state, + value, + crc_result(running_crc)); + + // When the parser is processing a message byte the running + // CRC for the message should be updated. This is done by + // resetting the running CRC to the init value when the + // parser is waiting and adding the received value to the + // CRC in all other states. + let running_crc_next = + case (parser_state) matches + tagged AwaitingOrderedSet: + crc_parameters.init; + default: + crc_add(running_crc, value_bits(value)); + endcase; + + parser_event <= tagged Valid + ParserEvent { + id: ev.id, + ev: tagged ParserResult { + running_crc: running_crc_next, + parser_state: parser_state_next, + character_valid: character_valid, + rd: fromMaybe( + RunningNegative, + decode_result.rd)}}; + + // Read the remaining receiver state from BRAM. + receive_state_2.a.put(False, ev.id, ?); + end + endcase + end + else if (decoder_event matches tagged Invalid &&& + receiver_events.notFull) begin + parser_event <= tagged Invalid; + end + + // + // Update receiver state. + // + + if (parser_event matches tagged Valid .ev &&& + receiver_events.notFull) begin + function Action enq_event(ReceiverEvent_$ev#(n) e) = + receiver_events.enq(ReceiverEvent {id: ev.id, ev: e}); + + let reset_receiver = False; + let link_status_change = False; + + let aligned = receive_state_2.a.read.aligned; + let locked = receive_state_2.a.read.locked; + let polarity_inverted = receive_state_2.a.read.polarity_inverted; + let expect_idle = receive_state_2.a.read.expect_idle; + let idle_set_valid_history = + receive_state_2.a.read.idle_set_valid_history; + let character_valid_history = + receive_state_2.a.read.character_valid_history; + + let rd_next = ev.ev.ParserResult.rd; + let parser_state_next = ev.ev.ParserResult.parser_state; + let running_crc_next = ev.ev.ParserResult.running_crc; + let aligned_next = aligned; + let locked_next = locked; + let polarity_inverted_next = polarity_inverted; + let expect_idle_next = expect_idle; + let idle_set_valid_history_next = idle_set_valid_history; + let character_valid_history_next = + shiftInAtN( + character_valid_history, + tagged Valid ev.ev.ParserResult.character_valid); + + if (ev.ev matches tagged ParserResult { + parser_state: .parser_state, + running_crc: .*, + character_valid: .*}) begin + // Consider the state of the parser and determine events + // impacting the link state. + Bool idle1 = False; + Bool idle2 = False; + Bool idle_inverted = False; + + if (parser_state matches tagged AwaitingReset .r) begin + parser_state_next = tagged AwaitingOrderedSet; + + case (r) matches + tagged Idle1 .inverted: begin + idle1 = True; + idle_inverted = inverted; + end + + tagged Idle2 .inverted: begin + idle2 = True; + idle_inverted = inverted; + end + endcase + end + + // A valid Idle1 or Idle2 set, with the correct polarity was + // received. Update the history accordingly. + if ((idle1 || idle2) && + (!idle_inverted || + (idle_inverted && !polarity_inverted))) begin + expect_idle_next = False; + idle_set_valid_history_next = + shiftInAt0( + idle_set_valid_history, + known_valid); + end + // An Idle set was expected to be completed by the received + // character but either the parser did not return a valid + // Idle set or the polarity was not as expected. Update the + // history accordingly. + else if (expect_idle) begin + expect_idle_next = False; + idle_set_valid_history_next = + shiftInAt0( + idle_set_valid_history, + known_invalid); + end + // Declare the deserializer aligned if the parser has seen a + // valid comma. The next received character should complete + // an Idle set. Setting this expectation will mean that if + // this was not a valid comma according to the parser, the + // idle_set_valid_history will be marked which in turn will + // trigger a receiver reset if alignment is off. + else if (parser.parsing_idle(parser_state)) begin + aligned_next = True; + expect_idle_next = True; + link_status_change = !aligned; + end + + // When an Idle set is received the running disparity of the + // link can be determined. This is idempotent and safe to + // set any time an Idle set is received. + if (idle1) begin + rd_next = RunningPositive; + end + else if (idle2) begin + rd_next = RunningNegative; + end + + // The link polarity gets to be adjusted once. Subsequent + // occurances of inverted Idle sets are recorded as Invalid + // in the Idle history and will trigger a link reset. + // + // It is tempting to restrict this even further and only + // allow this when the first slot in the Idle sets valid + // history is in unknown state, but a link may receive one + // or more invalid Idle sets as part of the alignment step. + // In such an instance any link with inverted polarity would + // be unable to start. + if (!polarity_inverted && idle_inverted) begin + polarity_inverted_next = True; + link_status_change = !polarity_inverted; + end + + // With most of the bookkeeping out of the way the only + // thing remaining is considering the receive history to + // determine if the link should be locked. + if (countElem(known_invalid, character_valid_history) > 2 || + idle_set_valid_history == + all_idle_sets_invalid) begin + reset_receiver = True; + end + else if (aligned && + character_valid_history == all_characters_valid && + idle_set_valid_history == all_idle_sets_valid) begin + locked_next = True; + link_status_change = !locked; + end + end + else begin + reset_receiver = True; + end + + // If the logic above triggered a receiver reset do so before + // writing back the state. + if (reset_receiver) begin + rd_next = RunningNegative; + parser_state_next = tagged AwaitingOrderedSet; + running_crc_next = crc_parameters.init; + aligned_next = False; + locked_next = False; + polarity_inverted_next = False; + expect_idle_next = False; + idle_set_valid_history_next = + IdleSetValidHistory'(replicate(unknown)); + character_valid_history_next = + CharacterValidHistory'(replicate(unknown)); + end + + receive_state_1_id <= tagged Valid ev.id; + receive_state_1_next <= + ReceiveState1 { + rd: rd_next, + parser_state: parser_state_next, + running_crc: running_crc_next}; + + receive_state_2_id <= tagged Valid ev.id; + receive_state_2_next <= + ReceiveState2 { + aligned: aligned_next, + locked: locked_next, + polarity_inverted: polarity_inverted_next, + expect_idle: expect_idle_next, + character_valid_history: character_valid_history_next, + idle_set_valid_history: idle_set_valid_history_next}; + + if (reset_receiver) begin + enq_event(tagged ReceiverReset); + end + else if (link_status_change) begin + enq_event(tagged ReceiverStatusChange + LinkStatus { + receiver_aligned: aligned_next, + receiver_locked: locked_next, + polarity_inverted: + polarity_inverted_next}); + end + else begin + case (ev.ev.ParserResult.parser_state) matches + tagged AwaitingReset .result: + case (result) matches + tagged Error .e: + case (e) + // OrderedSetInvalid errors may occur during + // link start-up. To avoid this noise being + // propagated in counters, suppress these + // errors specifically until the link is + // locked. Other errors are fair game even + // if the link is not locked since the + // parser should not produce them unless + // conditions are really bad/odd. + OrderedSetInvalid: + if (locked) + enq_event(tagged ReceiverEvent + link_events_ordered_set_invalid); + VersionInvalid: + enq_event(tagged ReceiverEvent + link_events_message_version_invalid); + MessageTypeInvalid: + enq_event(tagged ReceiverEvent + link_events_message_type_invalid); + ChecksumInvalid: + enq_event(tagged ReceiverEvent + link_events_message_checksum_invalid); + endcase + + tagged Message .*: + enq_event(tagged TargetStatusReceived); + endcase + + tagged ParsingSystemStatus .d: + enq_event(tagged StatusMessageFragment + tagged SystemType d); + + tagged ParsingSystemFaults .d: + enq_event(tagged StatusMessageFragment + tagged SystemStatus d); + + tagged ParsingRequestStatus .d: + enq_event(tagged StatusMessageFragment + tagged SystemEvents d); + + tagged ParsingLink0Status .d: + enq_event(tagged StatusMessageFragment + tagged SystemPowerRequestStatus d); + + tagged ParsingLink0Events .d: + enq_event(tagged StatusMessageFragment + tagged Link0Status d); + + tagged ParsingLink1Status .d: + enq_event(tagged StatusMessageFragment + tagged Link0Events d); + + tagged ParsingLink1Events .d: + enq_event(tagged StatusMessageFragment + tagged Link1Status d); + + tagged ComparingChecksum .d: + enq_event(tagged StatusMessageFragment + tagged Link1Events d); + endcase + end + end + else if (parser_event matches tagged Invalid &&& + receiver_events.notFull) begin + receive_state_1_id <= tagged Invalid; + receive_state_2_id <= tagged Invalid; + end + + // + // Write back receiver state. + // + + if (receive_state_1_id matches tagged Valid .id) begin + receive_state_1.b.put(True, id, receive_state_1_next); + end + + if (receive_state_2_id matches tagged Valid .id) begin + receive_state_2.b.put(True, id, receive_state_2_next); + + channels[id].set_search_for_comma(!receive_state_2_next.aligned); + channels[id].set_invert_polarity( + receive_state_2_next.polarity_inverted); + + channels_locked[id] <= + receive_state_2_next.aligned && + receive_state_2_next.locked; + end + endrule + + for (Integer id = 0; id < valueOf(n); id = id + 1) begin + (* fire_when_enabled *) + rule do_channel_locked_watchdog + (watchdog_fired && !channels_locked[fromInteger(id)]); + channels[fromInteger(id)].request_reset(); + endrule + end + + interface Vector rx = map(deserializer_client, channels); + interface Get events = toGet(receiver_events); + + method Action tick_1khz(); + // This automatically rolls over from 0, restarting the watchdog + // timer. + watchdog_ticks_remaining <= watchdog_ticks_remaining - 1; + watchdog_fired <= (watchdog_ticks_remaining == 0); + endmethod +endmodule + +typedef struct { + UInt#(TLog#(n)) id; + union tagged { + void DecoderReset; + Character Character; + } ev; +} DeserializerEvent#(numeric type n) deriving (Bits, Eq, FShow); + +typedef struct { + UInt#(TLog#(n)) id; + union tagged { + void ParserReset; + struct { + Bit#(8) running_crc; + StreamingStatusMessageParserState parser_state; + Encoding8b10b::DecodeResult result; + } DecodeResult; + } ev; +} DecoderEvent#(numeric type n) deriving (Bits, Eq, FShow); + +typedef struct { + UInt#(TLog#(n)) id; + union tagged { + void ReceiverReset; + struct { + Bit#(8) running_crc; + StreamingStatusMessageParserState parser_state; + Bool character_valid; + RunningDisparity rd; + } ParserResult; + } ev; +} ParserEvent#(numeric type n) deriving (Bits, Eq, FShow); + +typedef union tagged { + SystemType SystemType; + SystemStatus SystemStatus; + SystemFaults SystemEvents; + RequestStatus SystemPowerRequestStatus; + LinkStatus Link0Status; + LinkEvents Link0Events; + LinkStatus Link1Status; + LinkEvents Link1Events; +} StatusMessageFragment deriving (Bits, Eq, FShow); + +typedef struct { + UInt#(TLog#(n)) id; + union tagged { + void ReceiverReset; + LinkStatus ReceiverStatusChange; + LinkEvents ReceiverEvent; + StatusMessageFragment StatusMessageFragment; + void TargetStatusReceived; + } ev; +} ReceiverEvent#(numeric type n) deriving (Bits, Eq, FShow); + +typedef struct { + Bit#(8) running_crc; + StreamingStatusMessageParserState parser_state; + RunningDisparity rd; +} ReceiveState1 deriving (Bits); + +typedef struct { + IdleSetValidHistory idle_set_valid_history; + CharacterValidHistory character_valid_history; + Bool expect_idle; + Bool polarity_inverted; + Bool locked; + Bool aligned; +} ReceiveState2 deriving (Bits); + +function DeserializerClient deserializer_client(DeserializerChannel#(n) c) = c.deserializer; +function FIFOF#(DeserializerEvent#(n)) events(DeserializerChannel#(n) c) = c.events; +function Bool notEmpty(FIFOF#(t) f) = f.notEmpty; + +function Vector#(n, Bool) select_fifo( + Vector#(n, Bool) not_empty, + Bit#(n) grant_base); + let pending = {pack(not_empty), pack(not_empty)}; + + match {.left, .right} = split(pending & ~(pending - extend(grant_base))); + return unpack(left | right); +endfunction + +module mkRoundRobinFIFOFMux #( + Vector#(n, FIFOF#(t)) sources, + FIFOF#(t) sink) + (Empty); + Reg#(Bit#(n)) grant_base <- mkReg(1); + + // Determine which source should be selected next. + let grant = select_fifo(map(notEmpty, sources), grant_base); + + function Rules forwardRule(Integer i) = + rules + (* fire_when_enabled *) + rule do_forward (grant[i]); + sources[i].deq(); + sink.enq(sources[i].first); + grant_base <= rotateBitsBy(pack(grant), 1); + endrule + endrules; + + // The `select_fifo` function guarantees only a single bit will be set. + // These bits in the grant vector are used as guards on the rules above, but + // the compiler can not determine this. Add all the rules to the module and + // annotate them as mutually exclusive, allowing the compiler to schedule + // them as intended. + addRules(foldr( + rJoinMutuallyExclusive, + emptyRules, + map(forwardRule, Vector#(n, Integer)'(genVector)))); +endmodule + +function Vector#(y, Vector#(TDiv#(x, y), t)) + chunks( + Vector#(x, t) sources, + Vector#(y, t) sinks) + provisos (Add#(TDiv#(x, y), a__, x)); + function chunk(i) = takeAt(valueof(TDiv#(x, y)) * i, sources); + return map(chunk, genVector); +endfunction + +function module#(Empty) mkDeserializerEventMux1( + DeserializerEventFIFOs#(1, 1) deserializers, + FIFOF#(DeserializerEvent#(1)) deserializer_events_l0) = + mkRoundRobinFIFOFMux(deserializers, deserializer_events_l0); + +function module#(Empty) mkDeserializerEventMux2( + DeserializerEventFIFOs#(2, 2) deserializers, + FIFOF#(DeserializerEvent#(2)) deserializer_events_l0) = + mkRoundRobinFIFOFMux(deserializers, deserializer_events_l0); + +function module#(Empty) mkDeserializerEventMux4( + DeserializerEventFIFOs#(4, 4) deserializers, + FIFOF#(DeserializerEvent#(4)) deserializer_events_l0) = + mkRoundRobinFIFOFMux(deserializers, deserializer_events_l0); + +module mkDeserializerEventMux36 #( + DeserializerEventFIFOs#(36, 36) deserializers, + FIFOF#(DeserializerEvent#(36)) deserializer_events_l0) + (Empty); + Vector#(9, FIFOF#(DeserializerEvent#(36))) + deserializer_events_l2 <- replicateM(mkLFIFOF()); + zipWithM( + mkRoundRobinFIFOFMux, + chunks(deserializers, deserializer_events_l2), + deserializer_events_l2); + + Vector#(3, FIFOF#(DeserializerEvent#(36))) + deserializer_events_l1 <- replicateM(mkLFIFOF()); + zipWithM( + mkRoundRobinFIFOFMux, + chunks(deserializer_events_l2, deserializer_events_l1), + deserializer_events_l1); + + mkRoundRobinFIFOFMux(deserializer_events_l1, deserializer_events_l0); +endmodule + +// A 36:6:1 alternative mux, using only a single layer of 6 intermediate queues. +// This implementation seems to reduce utilization without any significant +// impact in timing and could be worth using instead of the 36:9:3:1 +// implementation above. +module mkDeserializerEventMux36Alt #( + DeserializerEventFIFOs#(36, 36) deserializers, + FIFOF#(DeserializerEvent#(36)) deserializer_events_l0) + (Empty); + Vector#(6, FIFOF#(DeserializerEvent#(36))) + deserializer_events_l1 <- replicateM(mkLFIFOF()); + zipWithM( + mkRoundRobinFIFOFMux, + chunks(deserializers, deserializer_events_l1), + deserializer_events_l1); + + mkRoundRobinFIFOFMux(deserializer_events_l1, deserializer_events_l0); +endmodule + +function module#(ControllerReceiver#(36)) mkControllerReceiver36() = + mkControllerReceiver(mkDeserializerEventMux36); + endpackage diff --git a/hdl/ip/bsv/ignition/IgnitionTarget.bsv b/hdl/ip/bsv/ignition/IgnitionTarget.bsv index eda4040d..93402090 100644 --- a/hdl/ip/bsv/ignition/IgnitionTarget.bsv +++ b/hdl/ip/bsv/ignition/IgnitionTarget.bsv @@ -276,7 +276,7 @@ module mkTarget #(Parameters parameters) (Target); let button_released = !fromMaybe(True, button_event_w.wget); // This Request RWire is used by rules to make system power requests. - RWire#(Request) request <- mkRWire(); + RWire#(SystemPowerRequest) request <- mkRWire(); Countdown#(12) system_power_toggle_cool_down <- mkCountdownBy1(); Reg#(Bool) system_power_toggle_cool_down_complete <- mkRegU(); @@ -358,7 +358,7 @@ module mkTarget #(Parameters parameters) (Target); reset_button_released) begin system_power_next = On; request_status <= - request_status_reset_in_progress | + request_status_power_reset_in_progress | request_status_power_on_in_progress; end // Complete an in progress system power request if the cool down has @@ -389,10 +389,10 @@ module mkTarget #(Parameters parameters) (Target); request_status <= request_status_power_off_in_progress; end - {On, SystemReset}: begin + {On, SystemPowerReset}: begin system_power_next = Off; request_status <= - request_status_reset_in_progress | + request_status_power_reset_in_progress | request_status_power_off_in_progress; end endcase @@ -524,13 +524,13 @@ module mkTarget #(Parameters parameters) (Target); // system power fault/abort. let request_ = system_power_off ? SystemPowerOn : - SystemReset; + SystemPowerReset; reset_button_released <= False; request.wset(request_); $display( "%5t [Target] ", $time, - fshow(request_), " Request from button"); + fshow(request_), " request from button"); endrule (* fire_when_enabled *) @@ -556,7 +556,7 @@ module mkTarget #(Parameters parameters) (Target); request.wset(request_); $display( "%5t [Target] ", $time, fshow(request_), - " Request from button "); + " request from button "); endrule end endcase @@ -581,7 +581,7 @@ module mkTarget #(Parameters parameters) (Target); $display( "%5t [Target] ", $time, fshow(request_), - " Request from Controller %1d", rx.sender); + " request from Controller %1d", rx.sender); endrule (* fire_when_enabled *) @@ -595,7 +595,7 @@ module mkTarget #(Parameters parameters) (Target); $display( "%5t [Target] ", $time, fshow(request_), - " Request from Controller %1d ignored", rx.sender); + " request from Controller %1d ignored", rx.sender); endrule // diff --git a/hdl/ip/bsv/ignition/IgnitionTransceiver.bsv b/hdl/ip/bsv/ignition/IgnitionTransceiver.bsv index af951a0c..c81f61df 100644 --- a/hdl/ip/bsv/ignition/IgnitionTransceiver.bsv +++ b/hdl/ip/bsv/ignition/IgnitionTransceiver.bsv @@ -17,10 +17,19 @@ export mkLoopback; export mkLinkStatusLED; +export ControllerTransceiver(..); +export ControllerTransceiverClient(..); +export mkControllerTransceiver; +export mkControllerTransceiver1; +export mkControllerTransceiver2; +export mkControllerTransceiver4; +export mkControllerTransceiver36; + import BuildVector::*; import ConfigReg::*; import Connectable::*; import FIFO::*; +import FIFOF::*; import GetPut::*; import RevertingVirtualReg::*; import Vector::*; @@ -49,6 +58,7 @@ interface Transceiver; method LinkStatus status(); method LinkEvents events(); method Bool receiver_locked_timeout(); + method Bool receiver_reset_event(); endinterface interface Transceivers#(numeric type n); @@ -61,6 +71,7 @@ interface TransceiverClient; interface GetS#(Message) to_txr; interface PutS#(Message) from_txr; method Action monitor(LinkStatus status, LinkEvents events); + method Action receiver_reset_event(); // Strobe driving the shared receiver watchdog. method Bool tick_1khz(); endinterface @@ -114,6 +125,7 @@ module mkTransceivers (Transceivers#(n)); method events = rx.events[i] | tx[i].events; method receiver_locked_timeout = rx.locked_timeout[i]; + method receiver_reset_event = rx.reset_event[i]; endinterface); endfunction @@ -228,6 +240,11 @@ instance Connectable#(Transceiver, TransceiverClient); rule do_monitor; client.monitor(txr.status, txr.events); endrule + + (* fire_when_enabled *) + rule do_receiver_reset_event (txr.receiver_reset_event); + client.receiver_reset_event(); + endrule endmodule endinstance @@ -341,5 +358,107 @@ module mkLinkStatusLED method _read = _q; endmodule +module mkTransceivers6 (Transceivers#(6)); + (* hide *) Transceivers#(6) _txrs <- mkTransceivers(); + return _txrs; +endmodule + +module mkTransceivers8 (Transceivers#(8)); + (* hide *) Transceivers#(8) _txrs <- mkTransceivers(); + return _txrs; +endmodule + +// typedef struct { +// UInt#(TLog#(n)) id; +// ControllerMessage ev; +// } TransmitterEvent#(numeric type n) deriving (Bits, FShow); + +interface ControllerTransceiver#(numeric type n); + interface Vector#(n, Tuple2#(Bool, GetPut#(Bit#(1)))) serial; + interface Get#(ReceiverEvent#(n)) rx; + interface Put#(TransmitterEvent#(n)) tx; + method Action tick_1khz(); +endinterface + +interface ControllerTransceiverClient#(numeric type n); + interface Put#(ReceiverEvent#(n)) rx; + interface Get#(TransmitterEvent#(n)) tx; +endinterface + +module mkControllerTransceiver #( + function module#(Empty) mkDeserializerEventMux( + Vector#(n, FIFOF#(DeserializerEvent#(n))) sources, + FIFOF#(DeserializerEvent#(n)) sink)) + (ControllerTransceiver#(n)); + Vector#(n, Deserializer) deserializers <- replicateM(mkDeserializer); + ControllerReceiver#(n) receiver <- mkControllerReceiver(mkDeserializerEventMux); + + zipWithM(mkConnection, deserializers, receiver.rx); + + FIFO#(TransmitterEvent#(n)) tx_ev <- mkLFIFO(); + Vector#(n, Serializer) serializers <- replicateM(mkSerializer); + Vector#(n, ControllerTransmitter) + transmitters <- replicateM(mkControllerTransmitter); + Vector#(n, Reg#(Bool)) + transmitter_output_enabled <- replicateM(mkReg(False)); + + zipWithM(mkConnection, transmitters, serializers); + + for (Integer i = 0; i < valueOf(n); i = i + 1) begin + (* fire_when_enabled *) + rule do_mux_message_event ( + tx_ev.first.id == fromInteger(i) &&& + tx_ev.first.ev matches tagged Message .message); + transmitters[i].message.put(message); + tx_ev.deq; + endrule + + (* fire_when_enabled *) + rule do_mux_output_enabled_event ( + tx_ev.first.id == fromInteger(i) &&& + tx_ev.first.ev matches tagged OutputEnabled .enabled); + transmitter_output_enabled[tx_ev.first.id] <= enabled; + tx_ev.deq; + endrule + end + + function Tuple2#(Bool, GetPut#(Bit#(1))) + to_serial(Bool oe, Serializer s, Deserializer d) = + tuple2(oe, tuple2(s.serial, d.serial)); + + interface Vector serial = + zipWith3( + to_serial, + readVReg(transmitter_output_enabled), + serializers, + deserializers); + interface Get rx = receiver.events; + interface Put tx = toPut(tx_ev); + method tick_1khz = receiver.tick_1khz; +endmodule + +function module#(ControllerTransceiver#(1)) mkControllerTransceiver1() = + mkControllerTransceiver(mkDeserializerEventMux1); + +function module#(ControllerTransceiver#(2)) mkControllerTransceiver2() = + mkControllerTransceiver(mkDeserializerEventMux2); + +function module#(ControllerTransceiver#(4)) mkControllerTransceiver4() = + mkControllerTransceiver(mkDeserializerEventMux4); + +function module#(ControllerTransceiver#(36)) mkControllerTransceiver36() = + mkControllerTransceiver(mkDeserializerEventMux36); + +instance Connectable#( + ControllerTransceiver#(n), + ControllerTransceiverClient#(n)); + module mkConnection #( + ControllerTransceiver#(n) txr, + ControllerTransceiverClient#(n) client) + (Empty); + mkConnection(client.tx, txr.tx); + mkConnection(txr.rx, client.rx); + endmodule +endinstance endpackage diff --git a/hdl/ip/bsv/ignition/IgnitionTransmitter.bsv b/hdl/ip/bsv/ignition/IgnitionTransmitter.bsv index 584fa56f..5d3ed059 100644 --- a/hdl/ip/bsv/ignition/IgnitionTransmitter.bsv +++ b/hdl/ip/bsv/ignition/IgnitionTransmitter.bsv @@ -3,6 +3,11 @@ package IgnitionTransmitter; export Transmitter(..); export mkTransmitter; +export ControllerTransmitter(..); +export mkControllerTransmitter; +export TransmitterEvent(..); +export TransmitterEvent_$ev(..); + import Connectable::*; import DReg::*; import FIFO::*; @@ -164,4 +169,157 @@ instance Connectable#(Transmitter, Serializer); endmodule endinstance +interface ControllerTransmitter; + interface Put#(ControllerMessage) message; + interface Get#(Character) character; + method LinkEvents events(); +endinterface + +module mkControllerTransmitter (ControllerTransmitter); + FIFO#(ControllerMessage) in <- mkLFIFO(); + FIFO#(Character) out <- mkLFIFO(); + + Reg#(Phase) phase <- mkReg(Deparse); + + Reg#(IgnitionProtocolDeparser::State) deparser <- mkReg(defaultValue); + Reg#(Value) value <- mkRegU(); + CRC#(8) crc <- mkIgnitionCRC(); + + // State of the link. + Reg#(Bool) idle <- mkReg(True); + Reg#(Bool) even <- mkReg(True); + Reg#(RunningDisparity) rd <- mkReg(RunningNegative); + Reg#(UInt#(2)) burst_count <- mkRegU(); + + // Events + Reg#(Bool) message_accepted <- mkDReg(False); + Reg#(Bool) encoding_error <- mkDReg(False); + + let start_of_message = + (even && burst_count != 3 && deparser == EmitStartOfMessage); + + (* fire_when_enabled *) + rule do_deparse_message (phase == Deparse && (!idle || start_of_message)); + match {.deparser_, .value_} = + deparse_controller_message(deparser, in.first, crc.result); + + let done = (deparser == EmitEndOfMessage1); + + deparser <= deparser_; + value <= value_; + idle <= done; + + if (done) begin + message_accepted <= True; + burst_count <= burst_count + 1; + end + + // Clearing the CRC during EmitVersion allows the calculation to start + // during the Encode phase. + if (deparser == EmitVersion) + crc.clear(); + + // Encode the value just generated by the Deparser. + phase <= Encode; + endrule + + (* descending_urgency = "do_deparse_message, do_select_ordered_set" *) + rule do_select_ordered_set (phase == Deparse && idle); + if (!even && deparser == AwaitReset) + // A message with an odd number of characters was transmitted. + // Transmit EndOfMessage2 to align to the sequence boundary. + value <= end_of_message2; + else if (!even && rd == RunningPositive) + // The link is idle, odd and running positive. This means the link + // is in the middle of an Idle1 sequence, since the prior comma will + // have turned the running disparity from negative to positive. + value <= idle1; + else if (!even && rd == RunningNegative) + // The link is idle, odd and running negative. This means the link + // is in the middle of an Idle2 sequence, since the prior comma will + // have turned the running disparity from positive to negative. + value <= idle2; + else begin + // The link is idle and aligned. Transmit a comma and start the next + // idle set. Note that this will always flip the running disparity. + value <= comma; + + // An idle sequence is about to be transmitted, so reset the message + // burst count. + burst_count <= 0; + end + + // Keep the Deparser in reset during Idle sequences. + deparser <= EmitStartOfMessage; + + // Encode the value just selected. + phase <= Encode; + endrule + + (* fire_when_enabled *) + rule do_encode_value (phase == Encode); + let result = encode(value, rd); + + case (result.character) matches + tagged Invalid .c: begin + // If the result is invalid raise an error but do not change the + // state of the link. This can really only happen if this module + // tries encoding invalid K values so absent design bugs or + // flipped bits at runtime this should not occur. + // + // Skipping a character will result in missing characters on the + // receiver end, but the receiver will detect this either as a + // result of a decode error, a message not parsing properly or a + // checksum error. + out.enq(unpack(c)); + encoding_error <= True; + end + tagged Valid .c: begin + out.enq(c); + end + endcase + + even <= !even; + rd <= result.rd; + + // Perform the CRC calculation in parallel with decoding the value so + // its result will be available during the `Deparse` phase. + crc.add(value_bits(value)); + + // Select the next value for decode. + phase <= Deparse; + endrule + + (* fire_when_enabled *) + rule do_message_done (message_accepted); + in.deq(); + endrule + + interface Put message = toPut(in); + interface Get character = toGet(out); + + method events = + LinkEvents { + message_checksum_invalid: False, + message_type_invalid: False, + message_version_invalid: False, + ordered_set_invalid: False, + decoding_error: False, + encoding_error: encoding_error}; +endmodule + +instance Connectable#(ControllerTransmitter, Serializer); + module mkConnection #(ControllerTransmitter t, Serializer s) (Empty); + mkConnection(t.character, s.character); + endmodule +endinstance + +typedef struct { + UInt#(TLog#(n)) id; + union tagged { + Bool OutputEnabled; + ControllerMessage Message; + } ev; +} TransmitterEvent#(numeric type n) deriving (Bits, Eq, FShow); + endpackage diff --git a/hdl/ip/bsv/ignition/ignition_controller.rdl b/hdl/ip/bsv/ignition/ignition_controller.rdl index fc7a068b..8a8cc078 100644 --- a/hdl/ip/bsv/ignition/ignition_controller.rdl +++ b/hdl/ip/bsv/ignition/ignition_controller.rdl @@ -10,14 +10,14 @@ reg link_status { default hw = w; field { - desc = "Flag indicating the receiver is aligned to the clock"; + desc = "Flag indicating the receiver is aligned to the symbol boundaries"; } RECEIVER_ALIGNED[1] = 0; field { - desc = "Flag indicating the receiver is receiving valid characters"; + desc = "Flag indicating the receiver is receiving valid ordered sets"; } RECEIVER_LOCKED[1] = 0; field { - desc = "Flag indicating the polarity of the link is inverted"; - } POLARITY_INVERTED[1] = 0; + desc = "Flag indicating the receiver detected the polarity of the link to be inverted"; + } RECEIVER_POLARITY_INVERTED[1] = 0; }; reg link_events { @@ -60,25 +60,43 @@ addrmap ignition_controller { desc = "Register description of the Ignition Controller"; default regwidth = 8; - default sw = r; + default sw = rw; default hw = w; + reg { + name = "Controller Transceiver State"; + default sw = r; + default hw = w; + + field { + desc = "Flag indicating the receiver is aligned to the symbol boundaries"; + } RECEIVER_ALIGNED[1] = 0; + field { + desc = "Flag indicating the receiver is receiving valid ordered sets"; + } RECEIVER_LOCKED[1] = 0; + field { + desc = "Flag indicating the polarity of the link is inverted"; + } RECEIVER_POLARITY_INVERTED[1] = 0; + field { + desc = "Flag indicating the transmitter output is enabled"; + } TRANSMITTER_OUTPUT_ENABLED[1] = 0; + field { + desc = "Set when the transmitter output is enabled; + 0 = disabled, + 1 = enabled if receiver aligned, + 2 = enabled if Target system present, + 3 = always"; + } TRANSMITTER_OUTPUT_ENABLE_MODE[2] = 0; + } TRANSCEIVER_STATE; + reg { name = "Controller State"; field { - desc = "A Target is present and the TARGET_SYSTEM_TYPE, TARGET_SYSTEM_STATUS and TARGET_REQUEST_STATUS registers are valid"; + desc = "A Target is present and the TARGET_SYSTEM_TYPE, TARGET_SYSTEM_STATUS and TARGET_SYSTEM_POWER_REQUEST_STATUS registers are valid"; } TARGET_PRESENT[1] = 0; - field { - sw = rw; - hw = r; - desc = "Always transmit rather than wait for a Target to be present first"; - } ALWAYS_TRANSMIT[1] = 0; } CONTROLLER_STATE; - link_status CONTROLLER_LINK_STATUS; - CONTROLLER_LINK_STATUS->name = "Controller Link Status"; - reg { name = "Target System Type"; field { @@ -104,7 +122,7 @@ addrmap ignition_controller { } TARGET_SYSTEM_STATUS; reg { - name = "Target System Faults"; + name = "Target System Events"; field { desc = "Flag indicating a power fault in A3"; @@ -119,14 +137,16 @@ addrmap ignition_controller { desc = "Reserved fault flag"; } RESERVED2[1] = 0; field { - desc = "Flag indicating an unrecoverable fault was detected by the SP"; - } SP_FAULT[1] = 0; + desc = ""; + } SP[1] = 0; field { - desc = "Flag indicating an unrecoverable fault was detected by the RoT"; - } ROT_FAULT[1] = 0; - } TARGET_SYSTEM_FAULTS; + desc = ""; + } ROT[1] = 0; + } TARGET_SYSTEM_EVENTS; reg { + name = "Target System Power Request Status"; + field { desc = "Flag indicating a power off request is in progress"; } POWER_OFF_IN_PROGRESS[1] = 0; @@ -135,8 +155,20 @@ addrmap ignition_controller { } POWER_ON_IN_PROGRESS[1] = 0; field { desc = "Flag indicating a system reset request is in progress"; - } SYSTEM_RESET_IN_PROGRESS[1] = 0; - } TARGET_REQUEST_STATUS; + } POWER_RESET_IN_PROGRESS[1] = 0; + field { + desc = "Reserved"; + } RESERVED[1] = 0; + field { + hw = r; + sw = w; + woclr; + desc = "Send a system power request; + 1 = power off, + 2 = power on, + 3 = power reset"; + } SYSTEM_POWER_REQUEST[2] = 0; + } TARGET_SYSTEM_POWER_REQUEST_STATUS; link_status TARGET_LINK0_STATUS; TARGET_LINK0_STATUS->name = "Target Link 0 Status"; @@ -144,45 +176,183 @@ addrmap ignition_controller { link_status TARGET_LINK1_STATUS; TARGET_LINK1_STATUS->name = "Target Link 1 Status"; - reg { - name = "Target Request"; - default sw = rw; - default hw = rw; + counter TARGET_PRESENT_COUNT @0x80; + TARGET_PRESENT_COUNT->name = "Target Present Count"; + TARGET_PRESENT_COUNT.COUNT->desc = + "The number of positive edges on the Target present signal since last read"; - field { - desc = "Value indicating the kind of request"; - } KIND[1:0] = 0; - field { - desc = "Flag indicating a request is (still) pending"; - } PENDING[7:7] = 0; - } TARGET_REQUEST @0x8; + counter TARGET_TIMEOUT_COUNT; + TARGET_TIMEOUT_COUNT->name = "Target Timeout Count"; + TARGET_TIMEOUT_COUNT.COUNT->desc = + "The number of negative edges on the Target present signal since last read"; + + counter TARGET_STATUS_RECEIVED_COUNT; + TARGET_STATUS_RECEIVED_COUNT->name = "Target Status Received Count"; + TARGET_STATUS_RECEIVED_COUNT.COUNT->desc = + "The number of Status messages received from the Target since last read"; - counter CONTROLLER_STATUS_RECEIVED_COUNT @0x10; - CONTROLLER_STATUS_RECEIVED_COUNT->name = "Controller Status Received Count"; - CONTROLLER_STATUS_RECEIVED_COUNT.COUNT->desc = - "The number of Status messages received by the Controller since last reset"; + counter TARGET_STATUS_TIMEOUT_COUNT; + TARGET_STATUS_TIMEOUT_COUNT->name = "Target Status Timeout Count"; + TARGET_STATUS_TIMEOUT_COUNT.COUNT->desc = + "The number times a Status message failed to be received in the expected time windown since last read"; counter CONTROLLER_HELLO_SENT_COUNT; CONTROLLER_HELLO_SENT_COUNT->name = "Controller Hello Sent Count"; CONTROLLER_HELLO_SENT_COUNT.COUNT->desc = - "The number of hello messages sent by the Controller since last reset"; + "The number of Hello messages sent by the Controller since last read"; + + counter CONTROLLER_SYSTEM_POWER_REQUEST_SENT_COUNT; + CONTROLLER_SYSTEM_POWER_REQUEST_SENT_COUNT->name = "Controller System Power Request Sent Count"; + CONTROLLER_SYSTEM_POWER_REQUEST_SENT_COUNT.COUNT->desc = + "The number of SystemPowerRequest messages sent by the Controller since last read"; + + counter CONTROLLER_RECEIVER_RESET_COUNT; + CONTROLLER_RECEIVER_RESET_COUNT->name = "Controller Receiver Reset Count"; + CONTROLLER_RECEIVER_RESET_COUNT.COUNT->desc = + "The number of times the Controller receiver was reset since last read"; + + counter CONTROLLER_RECEIVER_ALIGNED_COUNT; + CONTROLLER_RECEIVER_ALIGNED_COUNT->name = "Controller Receiver Aligned Count"; + CONTROLLER_RECEIVER_ALIGNED_COUNT.COUNT->desc = + "The number of times the Controller receiver transitioned to aligned since last read"; + + counter CONTROLLER_RECEIVER_LOCKED_COUNT; + CONTROLLER_RECEIVER_LOCKED_COUNT->name = "Controller Receiver Locked Count"; + CONTROLLER_RECEIVER_LOCKED_COUNT.COUNT->desc = + "The number of times the Controller receiver transitioned to locked since last read"; + + counter CONTROLLER_RECEIVER_POLARITY_INVERTED_COUNT; + CONTROLLER_RECEIVER_POLARITY_INVERTED_COUNT->name = "Controller Receiver Polarity Inverted Count"; + CONTROLLER_RECEIVER_POLARITY_INVERTED_COUNT.COUNT->desc = + "The number of times the Controller receiver observed the polarity of the channel to be inverted since last read"; + + counter CONTROLLER_ENCODING_ERROR_COUNT; + CONTROLLER_ENCODING_ERROR_COUNT->name = "Controller Encoding Error Count"; + CONTROLLER_ENCODING_ERROR_COUNT.COUNT->desc = + "The number of encoding errors observed by the Controller transmitter since last read"; + + counter CONTROLLER_DECODING_ERROR_COUNT; + CONTROLLER_DECODING_ERROR_COUNT->name = "Controller Decoding Error Count"; + CONTROLLER_DECODING_ERROR_COUNT.COUNT->desc = + "The number of decoding errors observed by the Controller receiver since last read"; + + counter CONTROLLER_ORDERED_SET_INVALID_COUNT; + CONTROLLER_ORDERED_SET_INVALID_COUNT->name = "Controller Ordered Set Invalid Count"; + CONTROLLER_ORDERED_SET_INVALID_COUNT.COUNT->desc = + "The number of invalid ordered sets observed by the Controller receiver since last read"; + + counter CONTROLLER_MESSAGE_VERSION_INVALID_COUNT; + CONTROLLER_MESSAGE_VERSION_INVALID_COUNT->name = "Controller Message Version Invalid Count"; + CONTROLLER_MESSAGE_VERSION_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid version observed by the Controller receiver since last read"; + + counter CONTROLLER_MESSAGE_TYPE_INVALID_COUNT; + CONTROLLER_MESSAGE_TYPE_INVALID_COUNT->name = "Controller Message Type Invalid Count"; + CONTROLLER_MESSAGE_TYPE_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid type observed by the Controller receiver since last read"; + + counter CONTROLLER_MESSAGE_CHECKSUM_INVALID_COUNT; + CONTROLLER_MESSAGE_CHECKSUM_INVALID_COUNT->name = "Controller Message Checksum Invalid Count"; + CONTROLLER_MESSAGE_CHECKSUM_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid checksum observed by the Controller receiver since last read"; + + counter TARGET_LINK0_RECEIVER_RESET_COUNT; + TARGET_LINK0_RECEIVER_RESET_COUNT->name = "Target Link 0 Receiver Reset Count"; + TARGET_LINK0_RECEIVER_RESET_COUNT.COUNT->desc = + "The number of times the Target link 0 receiver was reset since last read"; + + counter TARGET_LINK0_RECEIVER_ALIGNED_COUNT; + TARGET_LINK0_RECEIVER_ALIGNED_COUNT->name = "Target Link 0 Receiver Aligned Count"; + TARGET_LINK0_RECEIVER_ALIGNED_COUNT.COUNT->desc = + "The number of times the Target link 0 receiver transitioned to aligned since last read"; + + counter TARGET_LINK0_RECEIVER_LOCKED_COUNT; + TARGET_LINK0_RECEIVER_LOCKED_COUNT->name = "Target Link 0 Receiver Locked Count"; + TARGET_LINK0_RECEIVER_LOCKED_COUNT.COUNT->desc = + "The number of times the Target link 0 receiver transitioned to locked since last read"; + + counter TARGET_LINK0_RECEIVER_POLARITY_INVERTED_COUNT; + TARGET_LINK0_RECEIVER_POLARITY_INVERTED_COUNT->name = "Target Link 0 Receiver Polarity Inverted Count"; + TARGET_LINK0_RECEIVER_POLARITY_INVERTED_COUNT.COUNT->desc = + "The number of times the Target link 0 receiver observed the polarity of the channel to be inverted since last read"; + + counter TARGET_LINK0_ENCODING_ERROR_COUNT; + TARGET_LINK0_ENCODING_ERROR_COUNT->name = "Target Link 0 Encoding Error Count"; + TARGET_LINK0_ENCODING_ERROR_COUNT.COUNT->desc = + "The number of encoding errors observed by the Target link 0 transceiver since last read"; + + counter TARGET_LINK0_DECODING_ERROR_COUNT; + TARGET_LINK0_DECODING_ERROR_COUNT->name = "Target Link 0 Decoding Error Count"; + TARGET_LINK0_DECODING_ERROR_COUNT.COUNT->desc = + "The number of decoding errors observed by the Target link 0 transceiver since last read"; + + counter TARGET_LINK0_ORDERED_SET_INVALID_COUNT; + TARGET_LINK0_ORDERED_SET_INVALID_COUNT->name = "Target Link 0 Ordered Set Invalid Count"; + TARGET_LINK0_ORDERED_SET_INVALID_COUNT.COUNT->desc = + "The number of invalid ordered sets observed by the Target link 0 transceiver since last read"; + + counter TARGET_LINK0_MESSAGE_VERSION_INVALID_COUNT; + TARGET_LINK0_MESSAGE_VERSION_INVALID_COUNT->name = "Target Link 0 Message Version Invalid Count"; + TARGET_LINK0_MESSAGE_VERSION_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid version observed by the Target link 0 transceiver since last read"; + + counter TARGET_LINK0_MESSAGE_TYPE_INVALID_COUNT; + TARGET_LINK0_MESSAGE_TYPE_INVALID_COUNT->name = "Target Link 0 Message Type Invalid Count"; + TARGET_LINK0_MESSAGE_TYPE_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid type observed by the Target link 0 transceiver since last read"; + + counter TARGET_LINK0_MESSAGE_CHECKSUM_INVALID_COUNT; + TARGET_LINK0_MESSAGE_CHECKSUM_INVALID_COUNT->name = "Target Link 0 Message Checksum Invalid Count"; + TARGET_LINK0_MESSAGE_CHECKSUM_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid checksum observed by the Target link 0 transceiver since last read"; + + counter TARGET_LINK1_RECEIVER_RESET_COUNT; + TARGET_LINK1_RECEIVER_RESET_COUNT->name = "Target Link 1 Receiver Reset Count"; + TARGET_LINK1_RECEIVER_RESET_COUNT.COUNT->desc = + "The number of times the Target link 1 receiver was reset since last read"; + + counter TARGET_LINK1_RECEIVER_ALIGNED_COUNT; + TARGET_LINK1_RECEIVER_ALIGNED_COUNT->name = "Target Link 1 Receiver Aligned Count"; + TARGET_LINK1_RECEIVER_ALIGNED_COUNT.COUNT->desc = + "The number of times the Target link 1 receiver transitioned to aligned since last read"; + + counter TARGET_LINK1_RECEIVER_LOCKED_COUNT; + TARGET_LINK1_RECEIVER_LOCKED_COUNT->name = "Target Link 1 Receiver Locked Count"; + TARGET_LINK1_RECEIVER_LOCKED_COUNT.COUNT->desc = + "The number of times the Target link 1 receiver transitioned to locked since last read"; + + counter TARGET_LINK1_RECEIVER_POLARITY_INVERTED_COUNT; + TARGET_LINK1_RECEIVER_POLARITY_INVERTED_COUNT->name = "Target Link 1 Receiver Polarity Inverted Count"; + TARGET_LINK1_RECEIVER_POLARITY_INVERTED_COUNT.COUNT->desc = + "The number of times the Target link 1 receiver observed the polarity of the channel to be inverted since last read"; + + counter TARGET_LINK1_ENCODING_ERROR_COUNT; + TARGET_LINK1_ENCODING_ERROR_COUNT->name = "Target Link 1 Encoding Error Count"; + TARGET_LINK1_ENCODING_ERROR_COUNT.COUNT->desc = + "The number of encoding errors observed by the Target link 1 transceiver since last read"; - counter CONTROLLER_REQUEST_SENT_COUNT; - CONTROLLER_REQUEST_SENT_COUNT->name = "Controller Request Sent Count"; - CONTROLLER_REQUEST_SENT_COUNT.COUNT->desc = - "The number of request messages sent by the Controller since last reset"; + counter TARGET_LINK1_DECODING_ERROR_COUNT; + TARGET_LINK1_DECODING_ERROR_COUNT->name = "Target Link 1 Decoding Error Count"; + TARGET_LINK1_DECODING_ERROR_COUNT.COUNT->desc = + "The number of decoding errors observed by the Target link 1 transceiver since last read"; - counter CONTROLLER_MESSAGE_DROPPED_COUNT; - CONTROLLER_MESSAGE_DROPPED_COUNT->name = "Controller Message Dropped Count"; - CONTROLLER_MESSAGE_DROPPED_COUNT.COUNT->desc = - "The number of messages dropped by the Controller since last reset"; + counter TARGET_LINK1_ORDERED_SET_INVALID_COUNT; + TARGET_LINK1_ORDERED_SET_INVALID_COUNT->name = "Target Link 1 Ordered Set Invalid Count"; + TARGET_LINK1_ORDERED_SET_INVALID_COUNT.COUNT->desc = + "The number of invalid ordered sets observed by the Target link 1 transceiver since last read"; - link_events CONTROLLER_LINK_EVENTS_SUMMARY @0x20; - CONTROLLER_LINK_EVENTS_SUMMARY->name = "Controller Link Events Summary"; + counter TARGET_LINK1_MESSAGE_VERSION_INVALID_COUNT; + TARGET_LINK1_MESSAGE_VERSION_INVALID_COUNT->name = "Target Link 1 Message Version Invalid Count"; + TARGET_LINK1_MESSAGE_VERSION_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid version observed by the Target link 1 transceiver since last read"; - link_events TARGET_LINK0_EVENTS_SUMMARY @0x30; - TARGET_LINK0_EVENTS_SUMMARY->name = "Target Link 0 Events Summary"; + counter TARGET_LINK1_MESSAGE_TYPE_INVALID_COUNT; + TARGET_LINK1_MESSAGE_TYPE_INVALID_COUNT->name = "Target Link 1 Message Type Invalid Count"; + TARGET_LINK1_MESSAGE_TYPE_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid type observed by the Target link 1 transceiver since last read"; - link_events TARGET_LINK1_EVENTS_SUMMARY @0x40; - TARGET_LINK1_EVENTS_SUMMARY->name = "Target Link 1 Events Summary"; + counter TARGET_LINK1_MESSAGE_CHECKSUM_INVALID_COUNT; + TARGET_LINK1_MESSAGE_CHECKSUM_INVALID_COUNT->name = "Target Link 1 Message Checksum Invalid Count"; + TARGET_LINK1_MESSAGE_CHECKSUM_INVALID_COUNT.COUNT->desc = + "The number of messages with invalid checksum observed by the Target link 1 transceiver since last read"; }; diff --git a/hdl/ip/bsv/ignition/test/BUILD b/hdl/ip/bsv/ignition/test/BUILD index 65881391..83a10ee6 100644 --- a/hdl/ip/bsv/ignition/test/BUILD +++ b/hdl/ip/bsv/ignition/test/BUILD @@ -5,7 +5,9 @@ bluespec_library('TestHelpers', 'IgnitionTestHelpers.bsv', ], deps = [ + '//hdl/ip/bsv/ignition:Controller', '//hdl/ip/bsv/ignition:Protocol', + '//hdl/ip/bsv/ignition:Transceiver', '//hdl/ip/bsv:Encoding8b10b', '//hdl/ip/bsv:TestUtils', '//hdl/ip/bsv/test:Encoding8b10bReference', @@ -74,15 +76,19 @@ bluesim_tests('ControllerTests', suite = 'ControllerTests.bsv', modules = [ 'mkPeriodicHelloTest', - 'mkTargetPresentTest', - 'mkTargetStateValidIfPresentTest', - 'mkTargetLinkStatusTest', + 'mkReadTargetSystemStatusController0PresentTest', + 'mkReadTargetSystemStatusController1PresentTest', + 'mkReadTargetSystemStatusSystemPowerAbortTest', + 'mkReadTargetSystemStatusSystemPowerEnabledTest', + 'mkReadTargetSystemTypeRegisterTest', + 'mkReceiverResetEventTest', 'mkSendSystemPowerOffRequestTest', 'mkSendSystemPowerOnRequestTest', - 'mkSendSystemResetRequestTest', - 'mkDropHelloTest', - 'mkDropRequestTest', + 'mkSendSystemPowerResetRequestTest', 'mkTargetLinkEventsTest', + 'mkTargetLinkStatusTest', + 'mkTargetPresentTest', + 'mkTargetStateValidIfPresentTest', ], deps = [ ':Bench', @@ -101,7 +107,7 @@ bluesim_tests('MessageParserTests', 'mkParseHelloTest', 'mkParseSystemPowerOnRequestTest', 'mkParseSystemPowerOffRequestTest', - 'mkParseSystemResetRequestTest', + 'mkParseSystemPowerResetRequestTest', 'mkParseStatusTest', 'mkParseVersionInvalidTest', 'mkParseMessageTypeInvalidTest', @@ -126,7 +132,7 @@ bluesim_tests('ControllerMessageParserTests', 'mkParseHelloTest', 'mkParseSystemPowerOnRequestTest', 'mkParseSystemPowerOffRequestTest', - 'mkParseSystemResetRequestTest', + 'mkParseSystemPowerResetRequestTest', 'mkParseStatusTest', 'mkParseVersionInvalidTest', 'mkParseMessageTypeInvalidTest', @@ -147,7 +153,7 @@ bluesim_tests('DeparserTests', 'mkDeparseHelloTest', 'mkDeparseSystemPowerOffRequestTest', 'mkDeparseSystemPowerOnRequestTest', - 'mkDeparseSystemResetRequestTest', + 'mkDeparseSystemPowerResetRequestTest', 'mkDeparseStatusTest', ], deps = [ @@ -187,6 +193,7 @@ bluesim_tests('ReceiverTests', 'mkResetAfterInvalidCommaLikeCharacter', 'mkLockedTimeoutTest', 'mkNoLockedTimeoutIfReceiverLockedTest', + 'mkControllerReceiverTest', ], deps = [ ':TestHelpers', @@ -268,9 +275,9 @@ bluesim_tests('IntegrationTests', 'mkControllerTargetPresentTest', 'mkNoLockedTimeoutIfReceiversLockedTest', 'mkReceiversLockedTimeoutTest', - 'mkTargetLinkEventsTest', + 'mkTargetLinkErrorEventsTest', 'mkTargetRoTFaultTest', - 'mkTargetSystemResetTest', + 'mkTargetSystemPowerResetTest', ], deps = [ ':IgnitionControllerAndTargetBench', @@ -285,7 +292,7 @@ bluesim_tests('IntegrationTests', # deps = [ # ':TestHelpers', # '//hdl/ip/bsv/ignition:Transceiver', -# '//hdl/ip/bsv/boards/ecp5_evn:Board', +# '//hdl/projects/ecp5_evn:Board', # ]) # yosys_design('transceiver_loopback_ecp5_evn_test_top', diff --git a/hdl/ip/bsv/ignition/test/ControllerBench.bsv b/hdl/ip/bsv/ignition/test/ControllerBench.bsv index 7be7b15a..7f29119a 100644 --- a/hdl/ip/bsv/ignition/test/ControllerBench.bsv +++ b/hdl/ip/bsv/ignition/test/ControllerBench.bsv @@ -1,6 +1,8 @@ package ControllerBench; +import ClientServer::*; import Connectable::*; +import GetPut::*; import StmtFSM::*; import Strobe::*; @@ -9,62 +11,130 @@ import TestUtils::*; import BenchTransceiver::*; import IgnitionController::*; import IgnitionProtocol::*; +import IgnitionReceiver::*; +import IgnitionTestHelpers::*; import IgnitionTransceiver::*; +import IgnitionTransmitter::*; interface ControllerBench; - interface Controller controller; - interface Link link; + interface Controller#(4) controller; - method Integer tick_duration(); + method Integer tick_period(); method UInt#(32) ticks_elapsed(); method Action reset_ticks_elapsed(); - method Action await_tick(); method Action await_ticks_elapsed(Integer n); - method Action pet_watchdog(); + method Stmt clear_counter(ControllerId#(4) controller, CounterId counter); + method Stmt receive_status_message( + ControllerId#(4) controller, + Message messge); + + method Action assert_target_present(ControllerId#(4) id, String msg); + method Stmt assert_counter_eq( + ControllerId#(4) controller, + CounterId counter, + UInt#(8) expected_count, + String msg); + method Action assert_controller_message_eq( + ControllerId#(4) controller, + ControllerMessage message, + String msg); endinterface -Integer tick_duration = 2**2; +//Integer tick_duration = 2**2; module mkControllerBench #(Parameters parameters, Integer watchdog_timeout_in_ticks) (ControllerBench); - (* hide *) Controller _controller <- mkController(parameters); - BenchTransceiver txr <- mkBenchTransceiver(); - Strobe#(2) tick <- mkPowerTwoStrobe(1, 0); - - mkConnection(txr, _controller.txr); - mkConnection(asIfc(tick), asIfc(_controller.tick_1khz)); + (* hide *) Controller#(4) _controller <- mkController(parameters, True); - mkFreeRunningStrobe(tick); + Reg#(UInt#(10)) bench_ticks_elapsed <- mkReg(0); + Reg#(UInt#(32)) controller_ticks_elapsed <- mkReg(0); - Reg#(UInt#(32)) ticks_elapsed_ <- mkReg(0); + PulseWire controller_tick <- mkPulseWire(); PulseWire reset_ticks_elapsed_ <- mkPulseWire(); - TestWatchdog wd <- mkTestWatchdog(tick_duration * watchdog_timeout_in_ticks); - (* fire_when_enabled *) - rule do_count_ticks (tick || reset_ticks_elapsed_); - ticks_elapsed_ <= (reset_ticks_elapsed_ ? 0 : ticks_elapsed_ + 1); + rule do_tick; + _controller.tick_1mhz(); + + let controller_tick_ = + bench_ticks_elapsed == + fromInteger(parameters.tick_period - 1); + + bench_ticks_elapsed <= controller_tick_ ? 0 : bench_ticks_elapsed + 1; + + if (reset_ticks_elapsed_) + controller_ticks_elapsed <= 0; + else if (controller_tick_) begin + controller_tick.send(); + controller_ticks_elapsed <= controller_ticks_elapsed + 1; + end endrule - interface IgnitionController controller = _controller; - interface Link link = txr.bench; + TestWatchdog wd <- mkTestWatchdog( + parameters.tick_period * + watchdog_timeout_in_ticks); - method ticks_elapsed = ticks_elapsed_; - method reset_ticks_elapsed = reset_ticks_elapsed_.send; - method tick_duration = tick_duration; + interface Controller controller = _controller; - method Action await_tick() = await(_controller.tick_1khz); + method tick_period = parameters.tick_period; + method ticks_elapsed = controller_ticks_elapsed; + method reset_ticks_elapsed = reset_ticks_elapsed_.send; + method Action await_tick() = await(controller_tick); method Action await_ticks_elapsed(Integer n) = - await(ticks_elapsed_ >= fromInteger(tick_duration * n)); - - method pet_watchdog = wd.send; + await(controller_ticks_elapsed >= fromInteger(n)); + + method Stmt clear_counter( + ControllerId#(4) controller_, + CounterId counter_) = + seq + _controller.counters.request.put( + CounterAddress { + controller: controller_, + counter: counter_}); + assert_get_any(_controller.counters.response); + endseq; + + method Stmt receive_status_message( + ControllerId#(4) controller_, + Message message) = + controller_receive_status_message( + _controller, + controller_, + message); + + method Action assert_target_present(ControllerId#(4) id, String msg) = + assert_true(_controller.presence_summary[id], msg); + + method Stmt assert_counter_eq( + ControllerId#(4) controller_, + CounterId counter_, + UInt#(8) expected_count, + String msg) = + seq + _controller.counters.request.put( + CounterAddress { + controller: controller_, + counter: counter_}); + assert_get_eq(_controller.counters.response, expected_count, msg); + endseq; + + method Action assert_controller_message_eq( + ControllerId#(4) controller_, + ControllerMessage message, + String msg) = + assert_get_eq( + _controller.txr.tx, + TransmitterEvent { + id: controller_, + ev: tagged Message message}, + msg); endmodule function Integer bench_cycles(ControllerBench b, Integer n_ticks) = - b.tick_duration * n_ticks; + b.tick_period * n_ticks; endpackage diff --git a/hdl/ip/bsv/ignition/test/ControllerMessageParserTests.bsv b/hdl/ip/bsv/ignition/test/ControllerMessageParserTests.bsv index d6813321..0505e0fb 100644 --- a/hdl/ip/bsv/ignition/test/ControllerMessageParserTests.bsv +++ b/hdl/ip/bsv/ignition/test/ControllerMessageParserTests.bsv @@ -108,12 +108,12 @@ module mkParseSystemPowerOffRequestTest (Empty); return _test; endmodule -module mkParseSystemResetRequestTest (Empty); +module mkParseSystemPowerResetRequestTest (Empty); (* hide *) Empty _test <- mkParserTest( - mk_request_sequence(SystemReset), 'hfd, - tagged Message tagged Request SystemReset, - "expected SystemReset Request"); + mk_request_sequence(SystemPowerReset), 'hfd, + tagged Message tagged Request SystemPowerReset, + "expected SystemPowerReset Request"); return _test; endmodule diff --git a/hdl/ip/bsv/ignition/test/ControllerTests.bsv b/hdl/ip/bsv/ignition/test/ControllerTests.bsv index f78ff9c8..f248a5f4 100644 --- a/hdl/ip/bsv/ignition/test/ControllerTests.bsv +++ b/hdl/ip/bsv/ignition/test/ControllerTests.bsv @@ -1,11 +1,14 @@ package ControllerTests; import Assert::*; +import ClientServer::*; import Connectable::*; import DefaultValue::*; +import FIFO::*; import GetPut::*; import StmtFSM::*; +import CounterRAM::*; import Strobe::*; import TestUtils::*; @@ -16,6 +19,9 @@ import IgnitionControllerRegisters::*; import IgnitionProtocol::*; import IgnitionTestHelpers::*; +import IgnitionReceiver::*; +import IgnitionTransceiver::*; +import IgnitionTransmitter::*; IgnitionController::Parameters parameters = defaultValue; @@ -24,42 +30,88 @@ module mkPeriodicHelloTest (Empty); mkControllerBench( parameters, 4 * parameters.protocol.hello_interval); - let controller = bench.controller; - let rx = tpl_1(bench.link.message); + Reg#(Bool) first_hello_interval <- mkReg(True); + + // The Controller will emit OutputEnabled events for transmitters. This test + // is not interested in these events, so discard them. + FIFO#(TransmitterEvent#(4)) tx <- mkLFIFO(); + + (* fire_when_enabled *) + rule do_filter_tx_output_enabled_events; + let ev <- bench.controller.txr.tx.get; + + case (ev.ev) matches + tagged OutputEnabled .*: noAction; + default: tx.enq(ev); + endcase + endrule + + function assert_controller_message_eq(id, expected, msg) = + assert_get_eq( + fifoToGet(tx), + TransmitterEvent { + id: id, + ev: tagged Message expected}, + msg); mkAutoFSM(seq - action - bench.link.set_connected(True, True); - // Reset Hello sent count. - reset_count(controller.registers.controller_hello_sent_count); - endaction - - action - assert_get_eq(rx, tagged Hello, "expected Hello message"); - // Reset the ticks elapsed counter to determine the interval between - // Hello messages. - bench.reset_ticks_elapsed(); - endaction - - repeat(3) action // This only fires twice, still not sure why. - // Expect the next Hello message .. - assert_get_eq(rx, tagged Hello, "expected Hello message"); - - // .. and test the interval between it and the previous message. - // Note that the expired timer for Hello messages fires one tick - // early, to allow for some slack between send/receive and the - // timout on the Target side. - assert_eq( - bench.ticks_elapsed, - fromInteger(parameters.protocol.hello_interval), - "expected the configured number of ticks between Hello messages"); - - bench.reset_ticks_elapsed(); - endaction - - assert_count( - controller.registers.controller_hello_sent_count, 3, - "expected 3 Hello messages sent"); + // Reset HelloSent count. + bench.clear_counter(0, HelloSent); + bench.clear_counter(1, HelloSent); + bench.clear_counter(2, HelloSent); + bench.clear_counter(3, HelloSent); + + repeat(4) seq + // Expect the next Hello messages .. + assert_controller_message_eq( + 0, tagged Hello, + "expected Hello from Controller 0"); + assert_controller_message_eq( + 1, tagged Hello, + "expected Hello from Controller 1"); + assert_controller_message_eq( + 2, tagged Hello, + "expected Hello from Controller 2"); + assert_controller_message_eq( + 3, tagged Hello, + "expected Hello from Controller 3"); + + // .. and test the interval between this set and the previous + // messages. + action + if (first_hello_interval) begin + first_hello_interval <= False; + assert_eq( + bench.ticks_elapsed, 0, + "expected Hello messages during the first tick"); + end + else begin + assert_eq( + bench.ticks_elapsed, + fromInteger(parameters.protocol.hello_interval + 1), + "expected the configured number of ticks between Hello messages"); + + bench.reset_ticks_elapsed(); + end + endaction + endseq + + bench.assert_counter_eq( + 0, HelloSent, 3, + "expected HelloSent count of 3 for Controller 0"); + bench.assert_counter_eq( + 1, HelloSent, 3, + "expected HelloSent count of 3 for Controller 1"); + bench.assert_counter_eq( + 2, HelloSent, 3, + "expected HelloSent count of 3 for Controller 2"); + bench.assert_counter_eq( + 3, HelloSent, 3, + "expected HelloSent count of 3 for Controller 3"); + + bench.assert_counter_eq( + 0, HelloSent, 0, + "expected HelloSent count to have cleared"); endseq); endmodule @@ -68,27 +120,65 @@ module mkTargetPresentTest (Empty); mkControllerBench( parameters, 5 * parameters.protocol.status_interval); - let controller = bench.controller; - let tx = tpl_2(bench.link.message); + mkDiscardTx(bench.controller); mkAutoFSM(seq - action - bench.link.set_connected(True, True); - reset_count(controller.registers.controller_status_received_count); - endaction - - repeat(3) tx.put(message_status_system_powered_on_link0_connected); - - action - await(controller.registers.controller_state.target_present == 1); - bench.reset_ticks_elapsed(); - - assert_count( - controller.registers.controller_status_received_count, 3, - "expected Status received count to be 3"); - endaction - - await(controller.registers.controller_state.target_present == 0); + // Reset StatusReceived count. + bench.clear_counter(0, StatusReceived); + bench.clear_counter(1, StatusReceived); + bench.clear_counter(2, StatusReceived); + bench.clear_counter(3, StatusReceived); + + bench.clear_counter(0, TargetPresent); + bench.clear_counter(1, TargetPresent); + bench.clear_counter(2, TargetPresent); + bench.clear_counter(3, TargetPresent); + + repeat(4) seq + bench.receive_status_message(0, + message_status_system_powered_on_link0_connected); + bench.receive_status_message(1, + message_status_system_powered_on_link0_connected); + bench.receive_status_message(2, + message_status_system_powered_on_link0_connected); + bench.receive_status_message(3, + message_status_system_powered_on_link0_connected); + endseq + + bench.assert_target_present(0, "expected Target 0 present"); + bench.assert_target_present(1, "expected Target 1 present"); + bench.assert_target_present(2, "expected Target 2 present"); + bench.assert_target_present(3, "expected Target 3 present"); + + bench.assert_counter_eq( + 0, StatusReceived, 3, + "expected StatusReceived count of 3 for Controller 0"); + bench.assert_counter_eq( + 1, StatusReceived, 3, + "expected StatusReceived count of 3 for Controller 1"); + bench.assert_counter_eq( + 2, StatusReceived, 3, + "expected StatusReceived count of 3 for Controller 2"); + bench.assert_counter_eq( + 3, StatusReceived, 3, + "expected StatusReceived count of 3 for Controller 3"); + + bench.assert_counter_eq( + 0, TargetPresent, 1, + "expected TargetPresent count of 1 for Controller 0"); + bench.assert_counter_eq( + 1, TargetPresent, 1, + "expected TargetPresent count of 1 for Controller 1"); + bench.assert_counter_eq( + 2, TargetPresent, 1, + "expected TargetPresent count of 1 for Controller 2"); + bench.assert_counter_eq( + 3, TargetPresent, 1, + "expected TargetPresent count of 1 for Controller 3"); + + bench.assert_counter_eq( + 0, StatusReceived, 0, + "expected StatusReceived count to have cleared"); endseq); endmodule @@ -96,58 +186,32 @@ module mkTargetStateValidIfPresentTest (Empty); ControllerBench bench <- mkControllerBench( parameters, - parameters.protocol.status_interval); - let controller = bench.controller; - let tx = tpl_2(bench.link.message); - - // Shortcuts to registers. - let controller_state = bench.controller.registers.controller_state; - let target_system_type = bench.controller.registers.target_system_type; - let target_system_status = bench.controller.registers.target_system_status; + 5 * parameters.protocol.status_interval); + mkDiscardTx(bench.controller); mkAutoFSM(seq - bench.link.set_connected(True, True); - - action - bench.await_tick(); - tx.put(message_status_system_powered_on_link0_connected); - endaction - - action - // Target presence is not yet valid after one Status message. - bench.await_tick(); - assert_not_set(controller_state.target_present, - "expected Target not present"); - assert_eq( - target_system_type, - defaultValue, - "expected Target system type not set"); - assert_eq( - target_system_status, - defaultValue, - "expected Target system status not set"); - endaction - - // Send additional Status messages. - repeat(3) action - bench.await_tick(); - tx.put(message_status_system_powered_on_link0_connected); - endaction - - action - bench.await_tick(); - assert_set( - controller_state.target_present, - "expected Target present"); - assert_eq( - target_system_type.system_type, - pack(IgnitionTestHelpers::target_system_type.id), - "expected Target system type 5"); - assert_eq( - target_system_status, - unpack(extend(pack(system_status_system_power_enabled))), - "expected (only) system power enabled bit set"); - endaction + assert_controller_register_eq( + bench.controller, 0, + TargetSystemType, SystemType'(defaultValue), + "Target system type set"); + + // Announce the Target. + bench.receive_status_message(0, + message_status_system_powered_on_link0_connected); + await(bench.controller.idle); + + assert_controller_register_eq( + bench.controller, 0, + TargetSystemType, IgnitionTestHelpers::target_system_type, + "Target system type not set"); + + // Let the Target time out. + await(!bench.controller.presence_summary[0]); + + assert_controller_register_eq( + bench.controller, 0, + TargetSystemType, SystemType'(defaultValue), + "Target system type set"); endseq); endmodule @@ -155,45 +219,71 @@ module mkTargetLinkStatusTest (Empty); ControllerBench bench <- mkControllerBench( parameters, - parameters.protocol.status_interval); - let controller = bench.controller; - let tx = tpl_2(bench.link.message); - - // Shortcuts to registers. - let controller_state = bench.controller.registers.controller_state; - let link0_status = bench.controller.registers.target_link0_status; - let link1_status = bench.controller.registers.target_link1_status; + 5 * parameters.protocol.status_interval); + mkDiscardTx(bench.controller); mkAutoFSM(seq - bench.link.set_connected(True, True); - - // Send Status messages. - repeat(4) action - bench.await_tick(); - tx.put(message_status_system_powered_on_controller0_present); - endaction - - // Expect the Target to be present. - action - bench.await_tick(); - assert_set( - controller_state.target_present, - "expected Target present"); - - // Test the Link 0 Status registers. - assert_set( - link0_status.receiver_aligned, - "expected link 0 receiver aligned"); - assert_set( - link0_status.receiver_locked, - "expected link 0 receiver locked"); - assert_not_set( - link0_status.polarity_inverted, - "expected no link 0 polarity inversion"); - - assert_not_eq(link0_status, link1_status, - "expected link status registers to not be equal"); - endaction + // Reset Target link 0 status counters. + bench.clear_counter(0, TargetLink0ReceiverReset); + bench.clear_counter(0, TargetLink0ReceiverAligned); + bench.clear_counter(0, TargetLink0ReceiverLocked); + bench.clear_counter(0, TargetLink0ReceiverPolarityInverted); + + repeat(4) seq + bench.receive_status_message( + 0, message_status_system_powered_on_link0_connected); + endseq + await(bench.controller.idle); + + assert_controller_register_eq( + bench.controller, 0, + TargetLink0Status, + IgnitionControllerRegisters::LinkStatus { + receiver_aligned: 1, + receiver_locked: 1, + receiver_polarity_inverted: 0}, + "Target link 0 not connected"); + + bench.assert_counter_eq( + 0, TargetLink0ReceiverReset, 0, + "receiver reset count for Target link 0"); + bench.assert_counter_eq( + 0, TargetLink0ReceiverAligned, 1, + "receiver aligned count for Target link 0"); + bench.assert_counter_eq( + 0, TargetLink0ReceiverLocked, 1, + "receiver locked count for Target link 0"); + bench.assert_counter_eq( + 0, TargetLink0ReceiverPolarityInverted, 0, + "receiver polarity inverted count for Target link 0"); + + // Send a Status update which has link 0 disconnected, causing this to + // be counted as a receiver reset. + bench.receive_status_message( + 0, message_status_system_powered_on_link0_disconnected); + await(bench.controller.idle); + + assert_controller_register_eq( + bench.controller, 0, + TargetLink0Status, + IgnitionControllerRegisters::LinkStatus { + receiver_aligned: 0, + receiver_locked: 0, + receiver_polarity_inverted: 0}, + "Target link 0 not disconnected"); + + bench.assert_counter_eq(0, + TargetLink0ReceiverReset, 1, + "receiver reset count for Target link 0"); + bench.assert_counter_eq(0, + TargetLink0ReceiverAligned, 0, + "receiver aligned count for Target link 0"); + bench.assert_counter_eq(0, + TargetLink0ReceiverLocked, 0, + "receiver locked count for Target link 0"); + bench.assert_counter_eq(0, + TargetLink0ReceiverPolarityInverted, 0, + "receiver polarity inverted count for Target link 0"); endseq); endmodule @@ -201,214 +291,275 @@ module mkTargetLinkEventsTest (Empty); ControllerBench bench <- mkControllerBench( parameters, - parameters.protocol.status_interval); - let controller = bench.controller; - let tx = tpl_2(bench.link.message); - - // Shortcuts to registers. - let target_link0_counters = bench.controller.registers.target_link0_counters; - let target_link1_counters = bench.controller.registers.target_link1_counters; - let link_events = - link_events_message_checksum_invalid | - link_events_message_version_invalid | - link_events_decoding_error; - - let counters_expected_not_zero = - IgnitionControllerRegisters::LinkEvents { - encoding_error: 0, - decoding_error: 1, - ordered_set_invalid: 0, - message_version_invalid: 1, - message_type_invalid: 0, - message_checksum_invalid: 1}; + 2 * parameters.protocol.status_interval); + mkDiscardTx(bench.controller); mkAutoFSM(seq - action - bench.link.set_connected(True, True); - - // Clear all Target link event counters. - target_link0_counters.summary <= ~defaultValue; - target_link1_counters.summary <= ~defaultValue; - endaction - - // Send Status messages. - repeat(4) action + bench.clear_counter(0, TargetLink0EncodingError); + bench.clear_counter(0, TargetLink0DecodingError); + bench.clear_counter(0, TargetLink0OrderedSetInvalid); + bench.clear_counter(0, TargetLink0MessageVersionInvalid); + bench.clear_counter(0, TargetLink0MessageTypeInvalid); + bench.clear_counter(0, TargetLink0MessageChecksumInvalid); + bench.clear_counter(0, TargetLink1EncodingError); + bench.clear_counter(0, TargetLink1DecodingError); + bench.clear_counter(0, TargetLink1OrderedSetInvalid); + bench.clear_counter(0, TargetLink1MessageVersionInvalid); + bench.clear_counter(0, TargetLink1MessageTypeInvalid); + bench.clear_counter(0, TargetLink1MessageChecksumInvalid); + + repeat(3) seq + bench.receive_status_message(0, message_status_with_link0_events( + message_status_system_powered_on_controller0_present, + // Add some link events. + link_events_message_checksum_invalid | + link_events_message_version_invalid | + link_events_decoding_error)); bench.await_tick(); - tx.put(message_status_with_link0_events( - message_status_system_powered_on_controller0_present, - link_events)); - endaction - - action - // Test link counter summary registers. - assert_eq( - target_link0_counters.summary, - counters_expected_not_zero, - "expected summary bits set"); - assert_eq( - target_link1_counters.summary, - defaultValue, - "expected summary bits unset"); - - // Test the counters. - assert_count( - target_link0_counters.decoding_error, 3, - "expected 3 decoding_error events"); - assert_count( - target_link0_counters.message_version_invalid, 3, - "expected 3 message_version_invalid events"); - assert_count( - target_link0_counters.message_checksum_invalid, 3, - "expected 3 message_checksum_invalid events"); - - // Test a link 1 counter to make sure no accidental sharing is - // happening. - assert_count( - target_link1_counters.decoding_error, 0, - "expected no decoding_error events on link 1"); - endaction - - assert_eq( - target_link0_counters.summary, - defaultValue, - "expected summary cleared"); + endseq + + // Test link 0 counters + bench.assert_counter_eq(0, + TargetLink0EncodingError, 0, + "link 0 encoding error count"); + bench.assert_counter_eq(0, + TargetLink0DecodingError, 3, + "link 0 decoding error count"); + bench.assert_counter_eq(0, + TargetLink0OrderedSetInvalid, 0, + "link 0 ordered set invalid count"); + bench.assert_counter_eq(0, + TargetLink0MessageVersionInvalid, 3, + "link 0 message version invalid count"); + bench.assert_counter_eq(0, + TargetLink0MessageTypeInvalid, 0, + "link 0 message type invalid count"); + bench.assert_counter_eq(0, + TargetLink0MessageChecksumInvalid, 3, + "link 0 message version invalid count"); + + // Test link 1 counters, to assert no accidental sharing. + bench.assert_counter_eq(0, + TargetLink1EncodingError, 0, + "link 1 encoding error count"); + bench.assert_counter_eq(0, + TargetLink1DecodingError, 0, + "link 1 decoding error count"); + bench.assert_counter_eq(0, + TargetLink1OrderedSetInvalid, 0, + "link 1 ordered set invalid count"); + bench.assert_counter_eq(0, + TargetLink1MessageVersionInvalid, 0, + "link 1 message version invalid count"); + bench.assert_counter_eq(0, + TargetLink1MessageTypeInvalid, 0, + "link 1 message type invalid count"); + bench.assert_counter_eq(0, + TargetLink1MessageChecksumInvalid, 0, + "link 1 message version invalid count"); endseq); endmodule -module mkSendRequestTest #(Request r, String msg) (Empty); +module mkSendRequestTest #( + SystemPowerRequest expected_request, + String msg) (Empty); ControllerBench bench <- mkControllerBench( parameters, 2 * parameters.protocol.status_interval); - let controller = bench.controller; - let rx = tpl_1(bench.link.message); - let tx = tpl_2(bench.link.message); + Bit#(8) expected_request_value = {extend(pack(expected_request)), 4'b0}; + Reg#(Bool) system_power_request_received <- mkReg(False); mkAutoFSM(seq - action - bench.link.set_connected(True, True); - reset_count(controller.registers.controller_request_sent_count); - endaction - - // Send Status messages. - repeat(4) action + bench.clear_counter(0, SystemPowerRequestSent); + + repeat(4) seq + bench.receive_status_message( + 0, message_status_system_powered_on_controller0_present); + endseq + await(bench.controller.idle); + bench.assert_target_present(0, "expected Target present"); + + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TargetSystemPowerRequestStatus, + op: tagged Write expected_request_value}); + await(bench.controller.idle); + + // The transmit FIFO may contain a Hello message. Discard these and + // wait for a system power request. + while (!system_power_request_received) action + let e <- bench.controller.txr.tx.get; + + if (e.id == 0 &&& e.ev matches tagged Message + {tagged Request .actual_request}) begin + system_power_request_received <= True; + assert_eq(actual_request, expected_request, msg); + end + endaction + + // Wait a few cycles for the event counter to be updated and assert + // the count. bench.await_tick(); - tx.put(message_status_system_powered_on_controller0_present); - endaction - - action - bench.await_tick(); - controller.registers.target_request <= - TargetRequest { - pending: 0, - kind: pack(r)}; - endaction - - // The request should be sent in the next two cycles. - assert_set( - controller.registers.target_request.pending, - "expected request pending"); - assert_not_set( - controller.registers.target_request.pending, - "expected request sent"); - - assert_get_eq(rx, tagged Request r, msg); - - assert_count( - controller.registers.controller_request_sent_count, 1, - "expected 1 request sent"); + bench.assert_counter_eq(0, + SystemPowerRequestSent, 1, + "system power request sent count"); endseq); endmodule module mkSendSystemPowerOffRequestTest (Empty); (* hide *) Empty _test <- - mkSendRequestTest(SystemPowerOff, "expected SystemPowerOff Request"); + mkSendRequestTest(SystemPowerOff, "expected SystemPowerOff request"); return _test; endmodule module mkSendSystemPowerOnRequestTest (Empty); (* hide *) Empty _test <- - mkSendRequestTest(SystemPowerOn, "expected SystemPowerOn Request"); + mkSendRequestTest(SystemPowerOn, "expected SystemPowerOn request"); return _test; endmodule -module mkSendSystemResetRequestTest (Empty); +module mkSendSystemPowerResetRequestTest (Empty); (* hide *) Empty _test <- - mkSendRequestTest(SystemReset, "expected SystemReset Request"); + mkSendRequestTest( + SystemPowerReset, + "expected SystemPowerReset request"); return _test; endmodule -module mkDropHelloTest (Empty); +module mkReceiverResetEventTest (Empty); ControllerBench bench <- mkControllerBench( parameters, parameters.protocol.status_interval); - let controller = bench.controller; - let tx = tpl_2(bench.link.message); - + mkDiscardTx(bench.controller); mkAutoFSM(seq - action - bench.link.set_connected(True, True); - reset_count(controller.registers.controller_message_dropped_count); - endaction + bench.clear_counter(0, ControllerReceiverReset); - // Send Hello messages. - repeat(11) action - bench.await_tick(); - tx.put(tagged Hello); - endaction + repeat(4) bench.controller.txr.rx.put( + ReceiverEvent { + id: 0, + ev: tagged ReceiverReset}); + + // Wait a few ticks for the Controller to process the events and update + // the counter. + repeat(3) bench.await_tick(); - bench.await_tick(); - assert_count( - controller.registers.controller_message_dropped_count, 10, - "expected 10 dropped messages counted"); + bench.assert_counter_eq(0, + ControllerReceiverReset, 3, + "receiver reset count"); endseq); endmodule -module mkDropRequestTest (Empty); +module mkReadTargetStatusRegisterTest #( + Message status_message, + RegisterId register, + value_t expected_value, + String msg) + (Empty) + provisos ( + Bits#(value_t, value_t_sz), + Add#(value_t_sz, 0, 8), + Eq#(value_t), + FShow#(value_t)); ControllerBench bench <- - mkControllerBench( - parameters, - parameters.protocol.status_interval); - - let controller = bench.controller; - let tx = tpl_2(bench.link.message); + mkControllerBench( + parameters, + parameters.protocol.status_interval); + mkDiscardTx(bench.controller); mkAutoFSM(seq - action - bench.link.set_connected(True, True); - reset_count(controller.registers.controller_message_dropped_count); - endaction + bench.receive_status_message(0, status_message); + await(bench.controller.idle); + assert_controller_register_eq( + bench.controller, 0, + register, expected_value, + msg); + endseq); +endmodule - // Send Request messages. - repeat(11) action - bench.await_tick(); - tx.put(tagged Request SystemReset); - endaction +module mkReadTargetSystemTypeRegisterTest (Empty); + (* hide *) Empty _test <- mkReadTargetStatusRegisterTest( + message_status_system_powered_on_link0_connected, + TargetSystemType, + TargetSystemType {system_type: 5}, + "target system type"); - bench.await_tick(); - assert_count( - controller.registers.controller_message_dropped_count, 10, - "expected 10 dropped messages counted"); - endseq); + return _test; +endmodule + +module mkReadTargetSystemStatusController0PresentTest (Empty); + (* hide *) Empty _test <- mkReadTargetStatusRegisterTest( + message_status_with_system_status( + message_status_none, + system_status_controller0_present), + TargetSystemStatus, + TargetSystemStatus { + controller0_detected: 1, + controller1_detected: 0, + system_power_enabled: 0, + system_power_abort: 0}, + "target system status"); + + return _test; +endmodule + +module mkReadTargetSystemStatusController1PresentTest (Empty); + (* hide *) Empty _test <- mkReadTargetStatusRegisterTest( + message_status_with_system_status( + message_status_none, + system_status_controller1_present), + TargetSystemStatus, + TargetSystemStatus { + controller0_detected: 0, + controller1_detected: 1, + system_power_enabled: 0, + system_power_abort: 0}, + "target system status"); + + return _test; +endmodule + +module mkReadTargetSystemStatusSystemPowerEnabledTest (Empty); + (* hide *) Empty _test <- mkReadTargetStatusRegisterTest( + message_status_with_system_status( + message_status_none, + system_status_system_power_enabled), + TargetSystemStatus, + TargetSystemStatus { + controller0_detected: 0, + controller1_detected: 0, + system_power_enabled: 1, + system_power_abort: 0}, + "target system status"); + + return _test; endmodule -// -// Helpers -// - -function Action reset_count( - ActionValue#(IgnitionControllerRegisters::Counter) r) = - action - let _ <- r; - endaction; - -function Action assert_count( - ActionValue#(IgnitionControllerRegisters::Counter) r, - Bit#(8) expected_count, - String msg) = - assert_av_eq(r, unpack(expected_count), msg); +module mkReadTargetSystemStatusSystemPowerAbortTest (Empty); + (* hide *) Empty _test <- mkReadTargetStatusRegisterTest( + message_status_with_system_status( + message_status_none, + system_status_system_power_abort), + TargetSystemStatus, + TargetSystemStatus { + controller0_detected: 0, + controller1_detected: 0, + system_power_enabled: 0, + system_power_abort: 1}, + "target system status"); + + return _test; +endmodule + +module mkDiscardTx #(Controller#(n) controller) (Empty); + (* fire_when_enabled *) + rule do_discard_tx; + let _ <- controller.txr.tx.get; + endrule +endmodule endpackage diff --git a/hdl/ip/bsv/ignition/test/DeparserTests.bsv b/hdl/ip/bsv/ignition/test/DeparserTests.bsv index 3c77e9e3..c2da955e 100644 --- a/hdl/ip/bsv/ignition/test/DeparserTests.bsv +++ b/hdl/ip/bsv/ignition/test/DeparserTests.bsv @@ -56,11 +56,11 @@ module mkDeparseSystemPowerOnRequestTest (Empty); "expected SystemPowerOn Request frame"); endmodule -module mkDeparseSystemResetRequestTest (Empty); +module mkDeparseSystemPowerResetRequestTest (Empty); (* hide *) Empty _test <- mkDeparserTest( - tagged Request SystemReset, 'hfd, - mk_request_sequence(SystemReset), - "expected SystemReset Request frame"); + tagged Request SystemPowerReset, 'hfd, + mk_request_sequence(SystemPowerReset), + "expected SystemPowerReset Request frame"); endmodule module mkDeparseStatusTest (Empty); diff --git a/hdl/ip/bsv/ignition/test/IgnitionControllerAndTargetBench.bsv b/hdl/ip/bsv/ignition/test/IgnitionControllerAndTargetBench.bsv index a3647337..1570ee24 100644 --- a/hdl/ip/bsv/ignition/test/IgnitionControllerAndTargetBench.bsv +++ b/hdl/ip/bsv/ignition/test/IgnitionControllerAndTargetBench.bsv @@ -1,5 +1,6 @@ package IgnitionControllerAndTargetBench; +import ClientServer::*; import ConfigReg::*; import Connectable::*; import DefaultValue::*; @@ -16,6 +17,7 @@ import TestUtils::*; import IgnitionController::*; import IgnitionProtocol::*; +import IgnitionReceiver::*; import IgnitionTarget::*; import IgnitionTransceiver::*; @@ -31,7 +33,7 @@ endinterface interface IgnitionControllerAndTargetBench; interface Target target; - interface Controller controller; + interface Controller#(1) controller; interface Link controller_to_target; interface Link target_to_controller; @@ -39,6 +41,10 @@ interface IgnitionControllerAndTargetBench; method Action pet_watchdog(); method Action set_target_system_faults(SystemFaults faults); + method Bool target_system_power_off(); + method Bool target_system_power_on(); + + method Bool controller_transmitter_output_enabled(); method Bool controller_receiver_locked_timeout(); interface Vector#(2, Bool) target_receiver_locked_timeout; @@ -86,7 +92,7 @@ module mkLink #( endmethod endmodule -Integer tick_duration = 1000; +// /Integer tick_duration = 1000; module mkIgnitionControllerAndTargetBench #( Parameters parameters, @@ -95,7 +101,11 @@ module mkIgnitionControllerAndTargetBench #( // // Bench tick. // - Strobe#(10) tick <- mkLimitStrobe(1, tick_duration, 0); + Strobe#(6) tick <- mkLimitStrobe(1, 50, 0); + Strobe#(10) target_tick <- + mkLimitStrobe(1, parameters.controller.tick_period, 0); + + mkConnection(asIfc(tick), asIfc(target_tick)); mkFreeRunningStrobe(tick); // @@ -103,32 +113,57 @@ module mkIgnitionControllerAndTargetBench #( // Target target_ <- mkTarget(parameters.target); TargetTransceiver target_txr <- mkTargetTransceiver(True); - - mkConnection(asIfc(tick), asIfc(target_.tick_1khz)); - Strobe#(3) target_tx_strobe <- mkLimitStrobe(1, 5, 0); SampledSerialIO#(5) target_io <- mkSampledSerialIOWithTxStrobe( target_tx_strobe, tuple2(target_txr.to_link, target_txr.from_link[0])); - mkConnection(target_txr, target_.txr); + // Connect the Target and transceiver manually so the transceiver tick can + // be driven at a higher rate, reducing the time between receiver resets + // (and shortening tests a bit). + mkConnection(target_txr.to_client, target_.txr.from_txr); + mkConnection(target_.txr.to_txr, target_txr.from_client); + + // In the actual application the transceiver watchdog tick is supposed to be + // connected to the 1 kHz strobe. Combined with a nine bit counter this + // results in a watchdog event every half second. This is appropriate for a + // real-world scenario, where these watchdogs are expected to help with link + // startup during cable hotplug events, but during simulation it requires a + // significant number of cycles (>10M) to even observe a single such + // watchdog event. + // + // Instead of connecting to the application tick, connect the receiver to + // the symbol tick. This results in a possible watchdog reset every 500 + // symbols, which still leaves plenty of time for the receiver to align and + // lock during simulation. It makes the simulation deviate from the real + // world, but this seems appropriate given that no other logic depends on + // the timing of the transceiver watchdog. + mkConnection(asIfc(tick), asIfc(target_txr.tick_1khz)); + + (* fire_when_enabled *) + rule do_target_txr_monitor; + target_.txr.monitor(target_txr.status, target_txr.events); + endrule + + mkConnection(asIfc(target_tick), asIfc(target_.tick_1khz)); mkFreeRunningStrobe(target_tx_strobe); // // Controller, transceiver and IO adapter. // - Controller controller_ <- mkController(parameters.controller); - Transceiver controller_txr <- mkTransceiver(tick); + Controller#(1) controller_ <- mkController(parameters.controller, True); + ControllerTransceiver#(1) controller_txr <- mkControllerTransceiver1(); - mkConnection(asIfc(tick), asIfc(controller_.tick_1khz)); + mkConnection(asIfc(tick), asIfc(controller_.tick_1mhz)); + mkConnection(asIfc(tick), asIfc(controller_txr.tick_1khz)); // Set this TX strobe ~180 degrees out of phase from Target TX. Strobe#(3) controller_tx_strobe <- mkLimitStrobe(1, 5, 3); SampledSerialIO#(5) controller_io <- mkSampledSerialIOWithTxStrobe( controller_tx_strobe, - controller_txr.serial); + controller_txr.serial[0].snd); mkConnection(controller_txr, controller_.txr); mkFreeRunningStrobe(controller_tx_strobe); @@ -141,9 +176,9 @@ module mkIgnitionControllerAndTargetBench #( target_io.rx, parameters.invert_link_polarity, // Mimic the hardware implementation where the output buffer of the - // Controller transmitter is only enabled if a Target is present or - // the `always_transmit` bit has been set. - tx_enabled(controller_)); + // Controller transmitter is enabled depending on the configured + // output enable mode. + controller_txr.serial[0].fst); Link target_to_controller_link <- mkLink( @@ -163,9 +198,9 @@ module mkIgnitionControllerAndTargetBench #( ReadOnly#(Bit#(1)) target_to_controller_link_status_led <- mkLinkStatusLED( - controller_.status.target_present, - controller_txr.status, - controller_txr.receiver_locked_timeout, + controller_.presence_summary[0], + link_status_disconnected, //controller_txr.status, + False, //controller_txr.receiver_locked_timeout, False); // Generate single cycle timeout strobes on the positive edge for both @@ -178,12 +213,12 @@ module mkIgnitionControllerAndTargetBench #( (* fire_when_enabled *) rule do_past_receiver_locked_timeout; - past_controller_receiver_locked_timeout <= - controller_txr.receiver_locked_timeout; + // past_controller_receiver_locked_timeout <= + // controller_txr.receiver_locked_timeout; - controller_receiver_locked_timeout_ <= - !past_controller_receiver_locked_timeout && - controller_txr.receiver_locked_timeout; + // controller_receiver_locked_timeout_ <= + // !past_controller_receiver_locked_timeout && + // controller_txr.receiver_locked_timeout; for (Integer i = 0; i < 2; i = i + 1) begin past_target_receiver_locked_timeout[i] <= @@ -195,13 +230,11 @@ module mkIgnitionControllerAndTargetBench #( end endrule - (* fire_when_enabled *) - rule do_display_tick (tick); - $display("%5t [Bench] Tick", $time); - endrule - TestWatchdog wd <- - mkTestWatchdog(tick_duration * watchdog_timeout_in_ticks); + mkTestWatchdog( + parameters.controller.tick_period * + watchdog_timeout_in_ticks * + 50); (* fire_when_enabled *) rule do_set_system_type; @@ -224,6 +257,10 @@ module mkIgnitionControllerAndTargetBench #( method pet_watchdog = wd.send; method set_target_system_faults = target_faults._write; + method target_system_power_off = (target_.system_power == Off); + method target_system_power_on = (target_.system_power == On); + + method controller_transmitter_output_enabled = controller_txr.serial[0].fst; method controller_receiver_locked_timeout = controller_receiver_locked_timeout_; interface Vector target_receiver_locked_timeout = diff --git a/hdl/ip/bsv/ignition/test/IgnitionTestHelpers.bsv b/hdl/ip/bsv/ignition/test/IgnitionTestHelpers.bsv index 54dee7ae..b0f03bf4 100644 --- a/hdl/ip/bsv/ignition/test/IgnitionTestHelpers.bsv +++ b/hdl/ip/bsv/ignition/test/IgnitionTestHelpers.bsv @@ -1,14 +1,19 @@ package IgnitionTestHelpers; import BuildVector::*; +import ClientServer::*; import GetPut::*; +import StmtFSM::*; import Vector::*; import Encoding8b10b::*; import Encoding8b10bReference::*; import TestUtils::*; +import IgnitionController::*; import IgnitionProtocol::*; +import IgnitionReceiver::*; +import IgnitionTransceiver::*; Bit#(20) default_disconnect_pattern = '0; @@ -29,6 +34,17 @@ SystemStatus system_status_system_powered_on_both_controllers_present = system_status_controller1_present | system_status_controller0_present; +Message message_status_none = + tagged Status { + system_type: target_system_type, + system_status: defaultValue, + system_faults: system_faults_none, + request_status: request_status_none, + link0_status: link_status_disconnected, + link0_events: link_events_none, + link1_status: link_status_disconnected, + link1_events: link_events_none}; + Message message_status_system_powering_on = tagged Status { system_type: target_system_type, @@ -75,6 +91,17 @@ Message message_status_system_powered_on_link0_connected = link1_status: link_status_disconnected, link1_events: link_events_none}; +Message message_status_system_powered_on_link0_disconnected = + tagged Status { + system_type: target_system_type, + system_status: system_status_system_power_enabled, + system_faults: system_faults_none, + request_status: request_status_none, + link0_status: link_status_disconnected, + link0_events: link_events_none, + link1_status: link_status_disconnected, + link1_events: link_events_none}; + Message message_status_system_powered_on_controller0_present = tagged Status { system_type: target_system_type, @@ -189,6 +216,19 @@ Message message_status_system_power_abort_a2_fault_controller0_present = link1_status: link_status_disconnected, link1_events: link_events_none}; +function Message message_status_with_system_status( + Message m, + SystemStatus system_status) = + tagged Status { + system_type: m.Status.system_type, + system_status: system_status, + system_faults: m.Status.system_faults, + request_status: m.Status.request_status, + link0_status: m.Status.link0_status, + link0_events: m.Status.link0_events, + link1_status: m.Status.link1_status, + link1_events: m.Status.link1_events}; + function Message message_status_with_link0_status( Message m, LinkStatus link0_status) = @@ -289,7 +329,7 @@ ValueSequence#(5) hello_sequence = vec( tagged D 'hf0, end_of_message1); -function ValueSequence#(6) mk_request_sequence(Request r) = vec( +function ValueSequence#(6) mk_request_sequence(SystemPowerRequest r) = vec( start_of_message, tagged D fromInteger(defaultValue.version), tagged D 3, @@ -298,7 +338,7 @@ function ValueSequence#(6) mk_request_sequence(Request r) = vec( (case (r) SystemPowerOff: 'ha3; SystemPowerOn: 'hd2; - SystemReset: 'hfd; + SystemPowerReset: 'hfd; endcase), end_of_message1); @@ -416,4 +456,98 @@ function Action assert_character_rdp_get_display( $display(fshow(actual), " (%h)", actual.x); endaction; +function Stmt controller_receive_status_message( + Controller#(n) controller, + ControllerId#(n) controller_id, + Message message); + return seq + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged StatusMessageFragment + tagged SystemType + message.Status.system_type}); + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged StatusMessageFragment + tagged SystemStatus + message.Status.system_status}); + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged StatusMessageFragment + tagged SystemEvents + message.Status.system_faults}); + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged StatusMessageFragment + tagged SystemPowerRequestStatus + message.Status.request_status}); + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged StatusMessageFragment + tagged Link0Status message.Status.link0_status}); + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged StatusMessageFragment + tagged Link0Events message.Status.link0_events}); + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged StatusMessageFragment + tagged Link1Status message.Status.link1_status}); + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged StatusMessageFragment + tagged Link1Events message.Status.link1_events}); + controller.txr.rx.put( + ReceiverEvent { + id: controller_id, + ev: tagged TargetStatusReceived}); + endseq; +endfunction + + +function Stmt assert_controller_counter_eq( + Controller#(n) controller, + ControllerId#(n) controller_id, + CounterId counter_id, + UInt#(8) expected_count, + String msg) = + seq + controller.counters.request.put( + CounterAddress { + controller: controller_id, + counter: counter_id}); + assert_get_eq(controller.counters.response, expected_count, msg); + endseq; + +function Stmt assert_controller_register_eq( + Controller#(n) controller, + ControllerId#(n) controller_id, + RegisterId register_id, + value_t expected_value, + String msg) + provisos ( + Bits#(value_t, value_t_sz), + Add#(value_t_sz, a__, 8), + Eq#(value_t), + FShow#(value_t)) = + seq + controller.registers.request.put( + RegisterRequest { + id: controller_id, + register: register_id, + op: tagged Read}); + action + let data <- controller.registers.response.get; + assert_eq(unpack(truncate(data)), expected_value, msg); + endaction + endseq; + endpackage diff --git a/hdl/ip/bsv/ignition/test/IntegrationTests.bsv b/hdl/ip/bsv/ignition/test/IntegrationTests.bsv index 4397cb11..26c36523 100644 --- a/hdl/ip/bsv/ignition/test/IntegrationTests.bsv +++ b/hdl/ip/bsv/ignition/test/IntegrationTests.bsv @@ -1,6 +1,8 @@ package IntegrationTests; import Assert::*; +import ClientServer::*; +import GetPut::*; import StmtFSM::*; import TestUtils::*; @@ -23,6 +25,8 @@ IgnitionProtocol::Parameters protocol_parameters = IgnitionControllerAndTargetBench::Parameters parameters = Parameters { controller: IgnitionController::Parameters { + tick_period: 400, + transmitter_output_disable_timeout: 10, protocol: protocol_parameters}, target: IgnitionTarget::Parameters{ external_reset: True, @@ -30,7 +34,13 @@ IgnitionControllerAndTargetBench::Parameters parameters = mirror_link0_rx_as_link1_tx: False, system_type: tagged Valid target_system_type, button_behavior: ResetButton, - system_power_toggle_cool_down: 2, + // It takes a bit of time for Status updates to be applied in the + // Controller. This cool down controls how quickly the Target + // transitions from power off to power on during a system power + // reset and setting this too short may not give enough time to the + // bench to assert on the system power state. A value of 2 or less + // may produce falls negatives in the tests. + system_power_toggle_cool_down: 3, system_power_fault_monitor_enable: True, system_power_fault_monitor_start_delay: 2, system_power_hotswap_controller_restart: True, @@ -45,25 +55,33 @@ module mkControllerTargetPresentTest (Empty); 10 * max(protocol_parameters.hello_interval, protocol_parameters.status_interval)); - let controller_state = bench.controller.registers.controller_state; - let target_system_status = bench.controller.registers.target_system_status; - mkAutoFSM(seq action bench.controller_to_target.set_state(Connected); bench.target_to_controller.set_state(Connected); + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TransceiverState, + op: tagged Write extend( + {pack(EnabledWhenReceiverAligned), 4'h0})}); endaction par - await_set(controller_state.target_present); - await_set(target_system_status.controller0_detected); + await(bench.controller.presence_summary[0]); + await(bench.target.controller0_present); endpar + assert_controller_register_eq( + bench.controller, 0, TransceiverState, + link_status_connected, + "expected receiver aligned and locked"); + assert_set( - bench.target.leds[0], - "expected Target status LED set"); + bench.target.leds[0], + "expected Target status LED set"); assert_set( - bench.target.leds[1], - "expected system power status LED set"); + bench.target.leds[1], + "expected system power status LED set"); endseq); endmodule @@ -74,162 +92,237 @@ module mkTargetRoTFaultTest (Empty); 10 * max(protocol_parameters.hello_interval, protocol_parameters.status_interval)); - let controller_state = bench.controller.registers.controller_state; - let target_system_status = bench.controller.registers.target_system_status; - let target_system_faults = bench.controller.registers.target_system_faults; + Reg#(SystemFaults) target_system_events <- mkReg(defaultValue); + + function read_controller_0_register_into(id, d) = + read_controller_register_into(bench.controller, 0, id, asIfc(d)); + + function read_controller_0_registers_while(predicate) = + seq + while(predicate) seq + read_controller_0_register_into( + TargetSystemEvents, + target_system_events); + // Avoid a tight loop reading the registers otherwise the + // Controller will not make progress due to this interface + // having the highest priority. + repeat(3) bench.await_tick(); + endseq + endseq; mkAutoFSM(seq action bench.controller_to_target.set_state(Connected); bench.target_to_controller.set_state(Connected); + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TransceiverState, + op: tagged Write extend( + {pack(EnabledWhenReceiverAligned), 4'h0})}); endaction par - await_set(controller_state.target_present); - await_set(target_system_status.controller0_detected); + await(bench.controller.presence_summary[0]); + await(bench.target.controller0_present); endpar - // Assert no target system faults and set an RoT fault. - assert_eq( - target_system_faults, - defaultValue, - "expected no target system faults"); + // Assert no Target system faults and set an RoT fault. + assert_controller_register_eq( + bench.controller, 0, TargetSystemEvents, + system_faults_none, + "expected no target system faults"); bench.set_target_system_faults(system_faults_rot); // Assert the fault is observed by the Controller. - await_set(target_system_faults.rot_fault); - assert_set(target_system_faults.rot_fault, "expected RoT fault"); + read_controller_0_registers_while(!target_system_events.rot); + assert_controller_register_eq( + bench.controller, 0, TargetSystemEvents, + system_faults_rot, + "expected an RoT faults"); // Resolve the RoT fault. bench.set_target_system_faults(system_faults_none); // Assert the RoT fault cleared. - await_not_set(target_system_faults.rot_fault); - assert_eq( - target_system_faults, - defaultValue, - "expected no target system faults"); + read_controller_0_registers_while(target_system_events.rot); + assert_controller_register_eq( + bench.controller, 0, TargetSystemEvents, + system_faults_none, + "expected no target system faults"); endseq); endmodule -module mkTargetSystemResetTest (Empty); +module mkTargetSystemPowerResetTest (Empty); IgnitionControllerAndTargetBench bench <- mkIgnitionControllerAndTargetBench( parameters, 10 * max(protocol_parameters.hello_interval, protocol_parameters.status_interval)); - let controller_state = bench.controller.registers.controller_state; - let target_system_status = bench.controller.registers.target_system_status; - let target_request = asReg(bench.controller.registers.target_request); - let target_request_status = bench.controller.registers.target_request_status; + Reg#(IgnitionProtocol::SystemStatus) target_system_status <- mkReg(defaultValue); + Reg#(IgnitionProtocol::RequestStatus) target_system_power_request_status <- mkReg(defaultValue); + + function read_controller_0_register_into(id, d) = + read_controller_register_into(bench.controller, 0, id, asIfc(d)); + + function read_controller_0_registers_while(predicate) = + seq + while (predicate) seq + read_controller_0_register_into( + TargetSystemStatus, + target_system_status); + read_controller_0_register_into( + TargetSystemPowerRequestStatus, + target_system_power_request_status); + // Avoid a tight loop reading the registers otherwise the + // Controller will not make progress due to this interface + // having the highest priority. + repeat(3) bench.await_tick(); + endseq + endseq; mkAutoFSM(seq action bench.controller_to_target.set_state(Connected); bench.target_to_controller.set_state(Connected); + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TransceiverState, + op: tagged Write extend( + {pack(EnabledWhenReceiverAligned), 4'h0})}); endaction par - await_set(controller_state.target_present); - await_set(target_system_status.controller0_detected); + await(bench.controller.presence_summary[0]); + await_set(bench.target.controller0_present); endpar - // Await Target system power on to complete. - await(target_request_status == defaultValue); - assert_set( - target_system_status.system_power_enabled, - "expected Target system power enabled"); - - // Request a target system reset. - assert_eq(target_request, defaultValue, "expected no request queued"); - target_request <= TargetRequest {pending: 1, kind: 3}; + // Make sure all components agree Target system power is on and no + // system power requests are in progress. + par + await(bench.target_system_power_on); - // Await the request to be initiated on the Target. - await(target_request == defaultValue); - await(target_request_status != defaultValue); - assert_set( - target_request_status.system_reset_in_progress, - "expected system reset in progress"); - assert_set( - target_request_status.power_off_in_progress, - "expected power off in progress"); - assert_not_set( - target_system_status.system_power_enabled, - "expected target system power off"); - - // Await the system to be powering on. - await_not_set(target_request_status.power_off_in_progress); - assert_set( - target_request_status.system_reset_in_progress, - "expected system reset in progress"); - assert_set( - target_request_status.power_on_in_progress, - "expected power off in progress"); - assert_set( - target_system_status.system_power_enabled, - "expected target system power on"); + read_controller_0_registers_while( + !target_system_status.system_power_enabled || + target_system_power_request_status != request_status_none); + endpar - // Await the system reset request to complete. - await(target_request_status == defaultValue); + // Request a Target system power reset. + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TargetSystemPowerRequestStatus, + op: tagged Write ({extend(pack(SystemPowerReset)), 4'b0})}); + + // Observe the request being accepted by the Target and the system + // powering off. + read_controller_0_registers_while( + target_system_status.system_power_enabled || + target_system_power_request_status == request_status_none); + + assert_true( + bench.target_system_power_off, + "expected Target system power off"); + + // Observe system power bening enabled and the requested completed. + read_controller_0_registers_while( + !target_system_status.system_power_enabled || + target_system_power_request_status != request_status_none); + + assert_true( + bench.target_system_power_on, + "expected Target system power on"); endseq); endmodule -module mkTargetLinkEventsTest (Empty); +module mkTargetLinkErrorEventsTest (Empty); IgnitionControllerAndTargetBench bench <- mkIgnitionControllerAndTargetBench( parameters, 10 * max(protocol_parameters.hello_interval, protocol_parameters.status_interval)); - let controller_state = bench.controller.registers.controller_state; - let target_system_status = bench.controller.registers.target_system_status; - let target_link0_status = bench.controller.registers.target_link0_status; - let target_link0_events = - asReg(bench.controller.registers.target_link0_counters.summary); - let target_link1_events = - asReg(bench.controller.registers.target_link1_counters.summary); + Reg#(SystemStatus) target_system_status <- mkReg(defaultValue); + Reg#(LinkStatus) target_link0_status <- mkReg(defaultValue); + + Reg#(UInt#(8)) link0_decoding_errors <- mkReg(0); + Reg#(UInt#(8)) link1_decoding_errors <- mkReg(0); + + function read_controller_0_register_into(id, d) = + read_controller_register_into(bench.controller, 0, id, asIfc(d)); + + function read_controller_0_registers_while(predicate) = + seq + while(predicate) seq + read_controller_0_register_into( + TargetSystemStatus, + target_system_status); + read_controller_0_register_into( + TargetLink0Status, + target_link0_status); + // Avoid a tight loop reading the registers otherwise the + // Controller will not make progress due to this interface + // having the highest priority. + repeat(3) bench.await_tick(); + endseq + endseq; + + function clear_controller_0_counter(id) = + clear_controller_counter(bench.controller, 0, id); + + function assert_controller_0_counter_eq(id, expected_value, msg) = + assert_controller_counter_eq( + bench.controller, + 0, id, + expected_value, + msg); mkAutoFSM(seq action bench.controller_to_target.set_state(Connected); bench.target_to_controller.set_state(Connected); + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TransceiverState, + op: tagged Write extend( + {pack(EnabledWhenReceiverAligned), 4'h0})}); endaction + // Wait for the Target to report Controller presence through a Status + // message. par - await_set(controller_state.target_present); - await_set(target_system_status.controller0_detected); + await(bench.controller.presence_summary[0]); + read_controller_0_registers_while( + !target_system_status.controller0_present); endpar - // Reset the event summary for both target links and assert no events on - // link 0 afterwards. - action - target_link0_events <= unpack('1); - target_link1_events <= unpack('1); - endaction - assert_eq( - target_link0_events, unpack('0), - "expected no events for link 0"); + // Clear Target link event counters. + clear_controller_0_counter(TargetLink0DecodingError); + clear_controller_0_counter(TargetLink1DecodingError); - // Disturb the channel between the Controller transmitter and Target - // link 0 receiver, causing link events. + // Disturb the channel between Controller and Target, causing link error + // events and a receiver reset. bench.controller_to_target.set_state(Disconnected); - // Wait for the receiver to reset due to the errors. - await_not_set(target_link0_status.receiver_aligned); + // Wait for the Controller to have observed the Target link 0 state + // change before reading the event counters. This implicitly proves the + // Target receiver was reset as this will clear the link status bits. + read_controller_0_registers_while( + target_link0_status != link_status_disconnected); - assert_set( - target_link0_events.decoding_error, - "expected decoding error on link 0"); - assert_set( - target_link0_events.decoding_error, - "expected ordered_set_invalid on link 0"); - assert_eq( - target_link1_events, unpack('0), - "expected no events for link 1"); + assert_controller_0_counter_eq( + TargetLink0DecodingError, 2, + "link 0 decoding errors"); + + assert_controller_0_counter_eq( + TargetLink1DecodingError, 0, + "link 1 decoding errors"); endseq); endmodule // Verify that the Controller transmitter output enable override allows the -// Controller to start transmitting Hello messages before detecting the presence -// of a Target. +// Controller to start transmitting Hello messages before receiving from a +// Target. module mkControllerAlwaysTransmitOverrideTest (Empty); IgnitionControllerAndTargetBench bench <- mkIgnitionControllerAndTargetBench( @@ -237,33 +330,55 @@ module mkControllerAlwaysTransmitOverrideTest (Empty); 10 * max(protocol_parameters.hello_interval, protocol_parameters.status_interval)); - let controller_state = asReg(bench.controller.registers.controller_state); - let controller_link_status = bench.controller.registers.controller_link_status; - mkAutoFSM(seq - // Connect only the Controller->Target direction. This will keep the - // Controller from transmitting because it will not detect a Target to - // be present. + // Connect only the Controller->Target direction. This would normally + // keep the Controller from transmitting because it will not detect a + // Target on its receiver. bench.controller_to_target.set_state(Connected); - // Set the Controller to always transmit and await for the Target to - // detect the controller. - controller_state <= unpack('h02); - - // Assume the Controller has started sending Hello messages. Wait for - // the Controller to be marked present. - await(bench.target.controller0_present); - - // Assert the Controller has still not heard from the Target. - assert_not_set( - controller_link_status.receiver_aligned, - "expected receiver not aligned"); - assert_not_set( - controller_link_status.receiver_locked, - "expected receiver not locked"); - assert_not_set( - controller_state.target_present, - "expected no Target present"); + // Set the Controller to always transmit. + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TransceiverState, + op: tagged Write ({2'h0, pack(AlwaysEnabled), 4'h0})}); + + assert_controller_register_eq( + bench.controller, 0, TransceiverState, + {2'h0, pack(AlwaysEnabled), 1'b1, 3'h0}, + "expected transmitter output enabled and receiver not aligned"); + + // Await for the Transmitter output enable signal to be set and the + // Target to mark the Controller present after receiving Hello messages. + await(bench.controller_transmitter_output_enabled); + await_set(bench.target.controller0_present); + + // Assert the Controller receiver is still in a disconnected state. + assert_controller_register_eq( + bench.controller, 0, TransceiverState, + link_status_disconnected, + "expected receiver not aligned"); + + assert_false( + bench.controller.presence_summary[0], + "expected Target not present"); + + // Set the Controller to wait for a Target before transmitting. + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TransceiverState, + op: tagged Write + ({2'h0, pack(EnabledWhenReceiverAligned), 4'h0})}); + + // Wait for the disable timeout and the output enable to signal to be + // unset. + await(!bench.controller_transmitter_output_enabled); + + assert_controller_register_eq( + bench.controller, 0, TransceiverState, + {2'h0, pack(EnabledWhenReceiverAligned), 1'b0, 3'h0}, + "expected transmitter output disabled and receiver not aligned"); endseq); endmodule @@ -277,14 +392,20 @@ module mkReceiversLockedTimeoutTest (Empty); mkIgnitionControllerAndTargetBench(parameters, 1000); mkAutoFSM(seq + clear_controller_counter(bench.controller, 0, ControllerReceiverReset); + // The link between Controller and Target is not connected, causing both // receivers never to reach locked state. - par - repeat(3) await(bench.controller_receiver_locked_timeout); - repeat(3) await(bench.target_receiver_locked_timeout[0]); - repeat(3) await(bench.target_receiver_locked_timeout[1]); + repeat(4) await(bench.target_receiver_locked_timeout[0]); + repeat(4) await(bench.target_receiver_locked_timeout[1]); endpar + + assert_controller_counter_eq( + bench.controller, + 0, ControllerReceiverReset, + 3, + "expected Controller reset events"); endseq); endmodule @@ -296,41 +417,43 @@ module mkNoLockedTimeoutIfReceiversLockedTest (Empty); IgnitionControllerAndTargetBench bench <- mkIgnitionControllerAndTargetBench(parameters, 1100); - Reg#(int) controller_ticks <- mkReg(0); - Reg#(int) target_ticks <- mkReg(0); + Reg#(int) txr_watchdog_ticks <- mkReg(0); (* fire_when_enabled *) - rule do_count_controller_ticks (bench.controller.tick_1khz); - controller_ticks <= controller_ticks + 1; - endrule - - (* fire_when_enabled *) - rule do_count_target_ticks (bench.target.tick_1khz); - target_ticks <= target_ticks + 1; + rule do_count_txr_watchdog_ticks; + bench.await_tick(); + txr_watchdog_ticks <= txr_watchdog_ticks + 1; endrule continuousAssert( !bench.controller_receiver_locked_timeout, "expected no Controller receiver locked timeout"); - continuousAssert( - !bench.target_receiver_locked_timeout[0], - "expected no receiver locked timeout for Target link 0"); + (* fire_when_enabled *) + rule do_assert_target_receiver_locked_timeout + (bench.controller_transmitter_output_enabled); + assert_true(!bench.target_receiver_locked_timeout[0], + "expected no receiver locked timeout for Target link 0"); + endrule mkAutoFSM(seq action bench.controller_to_target.set_state(Connected); bench.target_to_controller.set_state(Connected); + bench.controller.registers.request.put( + RegisterRequest { + id: 0, + register: TransceiverState, + op: tagged Write ({2'h0, pack(AlwaysEnabled), 4'h0})}); endaction par // Both the Controller and Target link 0 should go for 1000 ticks // without a receiver timeout. - await(controller_ticks > 1000); - await(target_ticks > 1000); + await(txr_watchdog_ticks > 1000); // Target link 1 should time out three times during this period. - repeat(3) await(bench.target_receiver_locked_timeout[1]); + repeat(4) await(bench.target_receiver_locked_timeout[1]); endpar endseq); endmodule @@ -347,4 +470,16 @@ function Action await_not_set(one_bit_type v) provisos (Bits#(one_bit_type, 1)) = await(pack(v) == 0); +function Stmt clear_controller_counter( + Controller#(n) controller, + ControllerId#(n) controller_id, + CounterId counter_id) = + seq + controller.counters.request.put( + CounterAddress { + controller: controller_id, + counter: counter_id}); + assert_get_any(controller.counters.response); + endseq; + endpackage diff --git a/hdl/ip/bsv/ignition/test/IntegrationTests.gtkw b/hdl/ip/bsv/ignition/test/IntegrationTests.gtkw index dda6184f..24ebf144 100644 --- a/hdl/ip/bsv/ignition/test/IntegrationTests.gtkw +++ b/hdl/ip/bsv/ignition/test/IntegrationTests.gtkw @@ -5,7 +5,7 @@ [dumpfile] "/Users/arjen/oxidecomputer/quartz/build/vcd/IntegrationTests_mkControllerTargetPresentTest.vcd" [dumpfile_mtime] "Fri Sep 16 23:57:30 2022" [dumpfile_size] 1419853 -[savefile] "/Users/arjen/oxidecomputer/quartz/hdl/ip/bsv/ignition/test/IntegrationTests.gtkw" +[savefile] "/Users/arjen/oxidecomputer/quartz/hdl/ignition/test/IntegrationTests.gtkw" [timestart] 0 [size] 3440 1387 [pos] -1 -1 diff --git a/hdl/ip/bsv/ignition/test/MessageParserTests.bsv b/hdl/ip/bsv/ignition/test/MessageParserTests.bsv index e0cb44a1..6ed9d308 100644 --- a/hdl/ip/bsv/ignition/test/MessageParserTests.bsv +++ b/hdl/ip/bsv/ignition/test/MessageParserTests.bsv @@ -108,12 +108,12 @@ module mkParseSystemPowerOffRequestTest (Empty); return _test; endmodule -module mkParseSystemResetRequestTest (Empty); +module mkParseSystemPowerResetRequestTest (Empty); (* hide *) Empty _test <- mkParserTest( - mk_request_sequence(SystemReset), 'hfd, - tagged Message tagged Request SystemReset, - "expected SystemReset Request"); + mk_request_sequence(SystemPowerReset), 'hfd, + tagged Message tagged Request SystemPowerReset, + "expected SystemPowerReset Request"); return _test; endmodule diff --git a/hdl/ip/bsv/ignition/test/ReceiverTests.bsv b/hdl/ip/bsv/ignition/test/ReceiverTests.bsv index eb327948..e9476d59 100644 --- a/hdl/ip/bsv/ignition/test/ReceiverTests.bsv +++ b/hdl/ip/bsv/ignition/test/ReceiverTests.bsv @@ -17,6 +17,8 @@ import IgnitionProtocol::*; import IgnitionProtocolParser::*; import IgnitionReceiver::*; import IgnitionTransceiver::*; +import IgnitionTransmitter::*; +import IgnitionTestHelpers::*; interface MockTransmitter; @@ -302,4 +304,43 @@ module mkMockTransmitter #( endmethod endmodule +module mkControllerReceiverTest (Empty); + ControllerReceiver#(36) receiver <- mkControllerReceiver36(); + + FIFO#(Message) message <- mkLFIFO(); + Transmitter tx <- mkTransmitter(); + Vector#(36, FIFO#(DeserializedCharacter)) tx_buffers <- replicateM(mkSizedFIFO(3)); + + (* fire_when_enabled *) + rule do_buffer_next_character; + let c <- tx.character.get; + + for (Integer i = 0; i < 36; i = i + 1) begin + tx_buffers[i].enq(DeserializedCharacter {c: c, comma: ?}); + end + endrule + + for (Integer i = 0; i < 36; i = i + 1) begin + mkConnection(fifoToGetS(tx_buffers[i]), receiver.rx[i].character); + end + + mkConnection(fifoToGetS(message), tx.message); + + function Action receive_and_display_event() = + action + let e <- receiver.events.get; + $display("%05t: ", $time, fshow(e)); + endaction; + + mkAutoFSM(seq + repeat(36) receive_and_display_event(); // Reset + repeat(36) receive_and_display_event(); // Aligned + repeat(36) receive_and_display_event(); // Locked + message.enq(message_status_system_powered_on_link0_connected); + repeat(36 * 9) receive_and_display_event(); // Message + endseq); + + mkTestWatchdog(1000); +endmodule + endpackage diff --git a/hdl/ip/bsv/ignition/test/TargetTests.bsv b/hdl/ip/bsv/ignition/test/TargetTests.bsv index 105d9165..289a0eb0 100644 --- a/hdl/ip/bsv/ignition/test/TargetTests.bsv +++ b/hdl/ip/bsv/ignition/test/TargetTests.bsv @@ -329,7 +329,7 @@ module mkSystemResetRequestTest (Empty); system_status_system_powered_on_controller0_present); // Send a power off request and wait for confirmation. - tx.put(tagged Request SystemReset); + tx.put(tagged Request SystemPowerReset); // Expect a Status update indicating the system is powered off and a // reset is in progress. diff --git a/hdl/ip/bsv/ignition/test/TargetTransceiverTests.gtkw b/hdl/ip/bsv/ignition/test/TargetTransceiverTests.gtkw index d70a6187..6542dc5b 100644 --- a/hdl/ip/bsv/ignition/test/TargetTransceiverTests.gtkw +++ b/hdl/ip/bsv/ignition/test/TargetTransceiverTests.gtkw @@ -5,7 +5,7 @@ [dumpfile] "/Users/arjen/oxidecomputer/quartz/build/vcd/TargetTransceiverTests_mkStartUpLink0PolarityInvertedLink1PolarityInvertedTest.vcd" [dumpfile_mtime] "Thu Oct 27 03:09:17 2022" [dumpfile_size] 45502 -[savefile] "/Users/arjen/oxidecomputer/quartz/hdl/ip/bsv/ignition/test/TargetTransceiverTests.gtkw" +[savefile] "/Users/arjen/oxidecomputer/quartz/hdl/ignition/test/TargetTransceiverTests.gtkw" [timestart] 0 [size] 3440 1387 [pos] -1 -1 diff --git a/hdl/ip/bsv/test/BUILD b/hdl/ip/bsv/test/BUILD index 81369675..267b8870 100644 --- a/hdl/ip/bsv/test/BUILD +++ b/hdl/ip/bsv/test/BUILD @@ -42,3 +42,24 @@ bluesim_tests('BitSamplingTests', '//hdl/ip/bsv:BitSampling', '//hdl/ip/bsv:TestUtils', ]) + +bluesim_tests('CounterRAMTests', + env = 'bluesim_debug', + suite = 'CounterRAMTests.bsv', + modules = [ + 'mkAddSaturateTest', + 'mkConcurrentReadClearAndUpdateScenario1Test', + 'mkConcurrentReadClearAndUpdateScenario2Test', + 'mkConcurrentReadClearAndUpdateScenario3Test', + 'mkConcurrentReadClearAndUpdateScenario4Test', + 'mkMultipleAddMultipleCountersTest', + 'mkMultipleAddSingleCounterTest', + 'mkSetAndReadClearTest', + 'mkSetAndReadTest', + 'mkSubtractSaturateTest', + ], + deps = [ + '//hdl/ip/bsv:CounterRAM', + '//hdl/ip/bsv:TestUtils', + ]) + diff --git a/hdl/ip/bsv/test/CounterRAMTests.bsv b/hdl/ip/bsv/test/CounterRAMTests.bsv new file mode 100644 index 00000000..a3c8342c --- /dev/null +++ b/hdl/ip/bsv/test/CounterRAMTests.bsv @@ -0,0 +1,187 @@ +package CounterRAMTests; + +import ClientServer::*; +import GetPut::*; +import StmtFSM::*; + +import CounterRAM::*; +import TestUtils::*; + + +typedef Bit#(4) Id; +typedef CounterReadRequest#(Id) Read; +typedef CounterWriteRequest#(Id, 8) Write; +typedef CounterRAM#(Id, 8, 8) Counters; + +module mkSetAndReadTest (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 1}); + assert_false(counters.producer.idle, "expected producer not idle"); + await(counters.producer.idle); + + // Reading the counter without clearing it should yield the same value. + repeat(3) seq + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq( + counters.consumer.response, 1, + "expected a count of 1"); + endseq + endseq); + + mkTestWatchdog(100); +endmodule + +module mkSetAndReadClearTest (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 1}); + await(counters.producer.idle); + + counters.consumer.request.put(Read {id: 0, clear: True}); + assert_get_eq(counters.consumer.response, 1, "expected a count of 1"); + + // Reading the counter again should yield 0. + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq(counters.consumer.response, 0, "expected a count of 0"); + endseq); + + mkTestWatchdog(100); +endmodule + +module mkAddSaturateTest (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 1}); + counters.producer.request.put(Write {id: 0, op: Add, amount: 255}); + await(counters.producer.idle); + + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq( + counters.consumer.response, 255, + "expected a count of 255"); + endseq); + + mkTestWatchdog(100); +endmodule + +module mkSubtractSaturateTest (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 1}); + counters.producer.request.put(Write {id: 0, op: Subtract, amount: 2}); + await(counters.producer.idle); + + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq(counters.consumer.response, 0, "expected a count of 0"); + endseq); + + mkTestWatchdog(100); +endmodule + +module mkConcurrentReadClearAndUpdateScenario1Test (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 1}); + await(counters.producer.idle); + + action + counters.producer.request.put(Write {id: 0, op: Add, amount: 1}); + counters.consumer.request.put(Read {id: 0, clear: True}); + endaction + assert_get_eq(counters.consumer.response, 1, "expected a count of 1"); + + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq(counters.consumer.response, 1, "expected a count of 1"); + endseq); +endmodule + +module mkConcurrentReadClearAndUpdateScenario2Test (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 1}); + await(counters.producer.idle); + + counters.producer.request.put(Write {id: 0, op: Add, amount: 1}); + counters.consumer.request.put(Read {id: 0, clear: True}); + assert_get_eq(counters.consumer.response, 1, "expected a count of 1"); + + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq(counters.consumer.response, 1, "expected a count of 1"); + endseq); +endmodule + +module mkConcurrentReadClearAndUpdateScenario3Test (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 1}); + await(counters.producer.idle); + + counters.producer.request.put(Write {id: 0, op: Add, amount: 1}); + noAction; + counters.consumer.request.put(Read {id: 0, clear: True}); + assert_get_eq(counters.consumer.response, 2, "expected a count of 2"); + + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq(counters.consumer.response, 0, "expected a count of 0"); + endseq); +endmodule + +module mkConcurrentReadClearAndUpdateScenario4Test (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 1}); + await(counters.producer.idle); + + counters.consumer.request.put(Read {id: 0, clear: True}); + counters.producer.request.put(Write {id: 0, op: Add, amount: 1}); + assert_get_eq(counters.consumer.response, 1, "expected a count of 1"); + + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq(counters.consumer.response, 1, "expected a count of 1"); + endseq); +endmodule + +module mkMultipleAddSingleCounterTest (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 0}); + repeat(4) counters.producer.request.put( + Write {id: 0, op: Add, amount: 1}); + + await(counters.producer.idle); + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq(counters.consumer.response, 3, "expected a count of 3"); + endseq); +endmodule + +module mkMultipleAddMultipleCountersTest (Empty); + Counters counters <- mkCounterRAM(15); + + mkAutoFSM(seq + counters.producer.request.put(Write {id: 0, op: Set, amount: 0}); + counters.producer.request.put(Write {id: 1, op: Set, amount: 10}); + + repeat(4) seq + counters.producer.request.put(Write {id: 0, op: Add, amount: 1}); + counters.producer.request.put(Write {id: 1, op: Add, amount: 1}); + endseq + + counters.consumer.request.put(Read {id: 0, clear: False}); + assert_get_eq(counters.consumer.response, 3, "expected a count of 3"); + + counters.consumer.request.put(Read {id: 1, clear: False}); + assert_get_eq(counters.consumer.response, 13, "expected a count of 13"); + endseq); +endmodule + +endpackage diff --git a/hdl/projects/sidecar/mainboard/BUILD b/hdl/projects/sidecar/mainboard/BUILD index 30ca3225..09d7dd54 100644 --- a/hdl/projects/sidecar/mainboard/BUILD +++ b/hdl/projects/sidecar/mainboard/BUILD @@ -61,14 +61,14 @@ bluespec_library('SidecarMainboardController', ':PCIeEndpointController', ':Tofino2Sequencer', ':TofinoDebugPort', - '//hdl/ip/bsv:PowerRail', - '//hdl/ip/bsv/ignition:Controller', - '//hdl/ip/bsv/ignition:ControllerRegisters', - '//hdl/ip/bsv/ignition:Transceiver', '//hdl/ip/bsv:Debouncer', '//hdl/ip/bsv:GitVersion', + '//hdl/ip/bsv:PowerRail', '//hdl/ip/bsv:RegCommon', '//hdl/ip/bsv:WriteOnceReg', + '//hdl/ip/bsv/ignition:Controller', + '//hdl/ip/bsv/ignition:ControllerRegisters', + '//hdl/ip/bsv/ignition:Transceiver', '//hdl/ip/bsv/interfaces:SPI', ], using = { diff --git a/hdl/projects/sidecar/mainboard/SidecarMainboardController.bsv b/hdl/projects/sidecar/mainboard/SidecarMainboardController.bsv index aae3ddd9..061b0794 100644 --- a/hdl/projects/sidecar/mainboard/SidecarMainboardController.bsv +++ b/hdl/projects/sidecar/mainboard/SidecarMainboardController.bsv @@ -70,7 +70,7 @@ typedef struct { Bit#(1) clk_1hz; } Status deriving (Bits, Eq, FShow); -interface Pins #(numeric type n_ignition_controllers); +interface Pins; interface ClockGeneratorPins clocks; interface PCIeEndpointController::Pins pcie; interface Tofino2Sequencer::Pins tofino; @@ -80,7 +80,7 @@ interface Pins #(numeric type n_ignition_controllers); interface Vector#(4, FanModulePins) fans; endinterface -interface Registers #(numeric type n_ignition_controllers); +interface Registers; interface PCIeEndpointController::Registers pcie; interface Tofino2Sequencer::Registers tofino; interface TofinoDebugPort::Registers tofino_debug_port; @@ -89,31 +89,30 @@ interface Registers #(numeric type n_ignition_controllers); endinterface interface MainboardController #(numeric type n_ignition_controllers); - interface Pins#(n_ignition_controllers) pins; - interface Registers#(n_ignition_controllers) registers; - interface Vector#(n_ignition_controllers, Controller) ignition_controllers; + interface Pins pins; + interface Registers registers; + interface Controller#(n_ignition_controllers) ignition_controller; interface ReadOnly#(Status) status; + method Bool ignition_tick_1khz(); endinterface module mkMainboardController #(Parameters parameters) - (MainboardController#(n_ignition_controllers)) - provisos ( - Add#(TLog#(TAdd#(n_ignition_controllers, 1)), a__, 8)); + (MainboardController#(n_ignition_controllers)); // // Timing // - Strobe#(20) tick_1khz <- - mkLimitStrobe(1, (parameters.system_frequency_hz / 1000), 0); - Strobe#(10) tick_2hz <- mkLimitStrobe(1, 500, 0); + let limit_1mhz = fromInteger(parameters.system_frequency_hz / 1_000_000); + let limit_1khz = fromInteger(1_000_000 / 1000); + let limit_2hz = fromInteger(1000 / 2); - mkFreeRunningStrobe(tick_1khz); - mkConnection(asIfc(tick_1khz), asIfc(tick_2hz)); - - Strobe#(6) tick_1mhz <- - mkLimitStrobe(1, (parameters.system_frequency_hz / 1_000_000), 0); + Strobe#(6) tick_1mhz <- mkLimitStrobe(1, limit_1mhz, 0); + Strobe#(10) tick_1khz <- mkLimitStrobe(1, limit_1khz, 0); + Strobe#(9) tick_2hz <- mkLimitStrobe(1, limit_2hz, 0); mkFreeRunningStrobe(tick_1mhz); + mkConnection(asIfc(tick_1mhz), asIfc(tick_1khz)); + mkConnection(asIfc(tick_1khz), asIfc(tick_2hz)); // // Clock Generator sequencer @@ -176,15 +175,13 @@ module mkMainboardController #(Parameters parameters) // Ignition Controllers // - Vector#(n_ignition_controllers, IgnitionController::Controller) - ignition_controllers_ <- replicateM(mkController(defaultValue)); + IgnitionController::Controller#(n_ignition_controllers) + ignition <- mkController(defaultValue, False); - // Connect each Controller to the global tick. - function module#(Empty) mk_tick_connection( - IgnitionController::Controller controller) = - mkConnection(asIfc(tick_1khz), asIfc(controller.tick_1khz)); - - mapM(mk_tick_connection, ignition_controllers_); + (* fire_when_enabled *) + rule do_ignition_controller_tick_1mhz (tick_1mhz); + ignition.tick_1mhz(); + endrule // // Debug status @@ -224,8 +221,11 @@ module mkMainboardController #(Parameters parameters) interface fans = map(SidecarMainboardMiscSequencers::fan_registers, fans); endinterface - interface Vector ignition_controllers = ignition_controllers_; + interface Controller ignition_controller = ignition; + interface ReadOnly status = regToReadOnly(status_r); + + method ignition_tick_1khz = tick_1khz; endmodule endpackage diff --git a/hdl/projects/sidecar/mainboard/SidecarMainboardControllerSpiServer.bsv b/hdl/projects/sidecar/mainboard/SidecarMainboardControllerSpiServer.bsv index 72133e12..7d784d47 100644 --- a/hdl/projects/sidecar/mainboard/SidecarMainboardControllerSpiServer.bsv +++ b/hdl/projects/sidecar/mainboard/SidecarMainboardControllerSpiServer.bsv @@ -5,6 +5,7 @@ export SpiResponse(..); export SpiServer(..); export mkSpiServer; +import BuildVector::*; import ClientServer::*; import ConfigReg::*; import Connectable::*; @@ -13,6 +14,7 @@ import GetPut::*; import OInt::*; import Vector::*; +import CounterRAM::*; import git_version::*; import RegCommon::*; import WriteOnceReg::*; @@ -27,122 +29,117 @@ import Tofino2Sequencer::*; import TofinoDebugPort::*; -typedef struct { - RegOps op; - UInt#(8) address; - Bit#(8) wdata; -} PageRequest deriving (Bits, Eq); - -instance DefaultValue#(PageRequest); - defaultValue = - PageRequest { - op: NOOP, - address: ?, - wdata: ?}; -endinstance - module mkSpiServer #( Tofino2Sequencer::Registers tofino, TofinoDebugPort::Registers tofino_debug_port, PCIeEndpointController::Registers pcie, - Vector#(n_ignition_controllers, IgnitionController::Registers) ignition_pages, + IgnitionController::Controller#(n_ignition_controllers) ignition, Vector#(4, FanModuleRegisters) fans, Reg#(PowerRailState) front_io_hsc) (SpiServer) provisos ( - Add#(n_ignition_controllers, 4, n_pages), - Add#(TLog#(n_pages), a__, 8), - Add#(TLog#(n_ignition_controllers), b__, 8), - // Less than 40 Ignition Controllers. - Add#(n_ignition_controllers, c__, 40)); + NumAlias#(6, n_pages), + // Allow up to 64 Ignition Controllers given the SPI address + // width and the way Controllers are mapped into this space. + Add#(TLog#(n_ignition_controllers), a__, 6), + Add#(n_ignition_controllers, b__, 64)); Wire#(SpiRequest) in <- mkWire(); Wire#(SpiResponse) out <- mkWire(); - // The fan-in of requests and fan-out for responses is becoming wide, - // especially once Ignition Controller pages are added. In order to allow - // the placement process more freedom, latch the incoming request and - // generate a one-hot page select signal from the address MSB. In addition, - // a response register is allocated per page and the one-hot selector is - // used to select the appropriate response. This reduces the demux required - // to drive the response. - // - // This does come at the expense of one cycle to route the request to the - // selected page and one cycle to collect the response. - Vector#(n_pages, Reg#(Maybe#(PageRequest))) page_request <- replicateM(mkConfigReg(tagged Invalid)); - Vector#(n_pages, Reg#(SpiResponse)) page_response <- replicateM(mkConfigRegU()); - Reg#(Maybe#(OInt#(n_pages))) response_select <- mkRegU(); - - Reg#(Bool) request_in_progress <- mkDReg(False); - Reg#(Bool) select_response <- mkDReg(False); + Reg#(Maybe#(OInt#(n_pages))) response_select <- mkReg(tagged Invalid); + Reg#(UInt#(4)) cycles_until_response_needed <- mkRegU(); + + RegisterPage page0 <- mkPage0(fans, front_io_hsc); + RegisterPage page1 <- mkPage1(tofino, pcie); + RegisterPage page2 <- mkPage2(tofino_debug_port); + RegisterPage ignition_general_page <- + mkIgnitionGeneralPage(ignition); + RegisterPage ignition_registers_page <- + mkIgnitionRegistersPage(ignition.registers); + RegisterPage ignition_counters_page <- + mkIgnitionCountersPage(ignition.counters); + + Vector#(n_pages, RegisterPage) pages = + vec(page0, + page1, + page2, + ignition_general_page, + ignition_registers_page, + ignition_counters_page); (* fire_when_enabled *) rule do_select_page (!isValid(response_select)); - let page = in.address[15:8]; - let page_limit = fromInteger(valueOf(n_pages)); - - request_in_progress <= True; - response_select <= - page < page_limit && in.op == READ ? - tagged Valid toOInt(truncate(page)) : - tagged Invalid; + function ActionValue#(Bool) + offer_request(RegisterPage page) = page.offer(in); + + // Dispatch the incoming request to all pages, calling their `offer` + // method. This returns a vector of booleans indicating whether or not + // the page accepted the request. The assumption is that the acceptance + // criteria of the pages are mutually exclusive, resulting in the vector + // containing zero or one True values. + let accepted <- mapM(offer_request, pages); + + if (\or (accepted)) begin + // If one of the accept bits is set, store the vector as OInt, + // allowing one-hot selection of the response. + response_select <= tagged Valid unpack(pack(accepted)); + cycles_until_response_needed <= 8; + end + else begin + out <= SpiResponse {readdata: 'h00}; + end endrule - for (Integer i = 0; i < valueOf(n_pages); i = i + 1) begin - let page_selected = (in.address[15:8] == fromInteger(i)); - - (* fire_when_enabled *) - rule do_request_from_page (page_selected && !isValid(page_request[i])); - page_request[i] <= tagged Valid - PageRequest { - op: in.op, - address: unpack(in.address[7:0]), - wdata: in.wdata}; - endrule - end - (* fire_when_enabled *) - rule do_complete_request (request_in_progress && isValid(response_select)); - select_response <= True; + rule do_select_response (response_select matches tagged Valid .i); + function read_page(page) = page.read; + + if (cycles_until_response_needed == 0) begin + out <= select(map(read_page, pages), i); + response_select <= tagged Invalid; + end + else begin + cycles_until_response_needed <= cycles_until_response_needed - 1; + end endrule - (* fire_when_enabled *) - rule do_select_response (select_response && isValid(response_select)); - if (response_select matches tagged Valid .i) - out <= select(readVReg(page_response), i); - else - out <= SpiResponse {readdata: 8'h00}; + interface Put request; + method put if (!isValid(response_select)) = in._write; + endinterface - response_select <= tagged Invalid; - endrule + interface Get response = toGet(asIfc(out)); +endmodule - // - // Guard helpers for rules responding to page requests. - // +// +// Register pages +// - function Maybe#(PageRequest) read_page(Integer i) = - case (page_request[i]) matches - tagged Valid .r &&& (r.op == READ): page_request[i]; - default: tagged Invalid; - endcase; +interface RegisterPage; + method ActionValue#(Bool) offer(SpiRequest request); + method SpiResponse read(); +endinterface - function Maybe#(PageRequest) update_page(Integer i) = - case (page_request[i]) matches - tagged Valid .r &&& (r.op != NOOP &&& r.op != READ): - page_request[i]; - default: tagged Invalid; - endcase; +// +// Page 0 +// - // - // Page 0 - // +module mkPage0 #( + Vector#(4, FanModuleRegisters) fans, + Reg#(PowerRailState) front_io_hsc) + (RegisterPage); + Reg#(SpiResponse) response <- mkConfigRegU(); + Reg#(Maybe#(RegOps)) maybe_op <- mkReg(tagged Invalid); + Reg#(Bit#(8)) address <- mkRegU(); + Reg#(Bit#(8)) wdata <- mkRegU(); ConfigReg#(Scratchpad) scratchpad <- mkConfigReg(unpack('0)); - Vector#(4, ConfigReg#(Bit#(8))) checksum <- replicateM(mkWriteOnceConfigReg(0)); + Vector#(4, ConfigReg#(Bit#(8))) + checksum <- replicateM(mkWriteOnceConfigReg(0)); (* fire_when_enabled *) - rule do_page0_read (read_page(0) matches tagged Valid .request); + rule do_page1_read (maybe_op matches tagged Valid .op &&& op == READ); let reader = - case (request.address) + case (address) // ID, see RDL for the (default) values. fromOffset(id0Offset): read(Id0'(defaultValue)); fromOffset(id1Offset): read(Id1'(defaultValue)); @@ -182,38 +179,62 @@ module mkSpiServer #( default: read(8'hff); endcase; - let data <- reader; + let rdata <- reader; - page_request[0] <= tagged Invalid; - page_response[0] <= data; + maybe_op <= tagged Invalid; + response <= rdata; endrule (* fire_when_enabled *) - rule do_page0_update (update_page(0) matches tagged Valid .request); - case (request.address) - fromOffset(cs0Offset): update(request.op, checksum[0], request.wdata); - fromOffset(cs1Offset): update(request.op, checksum[1], request.wdata); - fromOffset(cs2Offset): update(request.op, checksum[2], request.wdata); - fromOffset(cs3Offset): update(request.op, checksum[3], request.wdata); - fromOffset(scratchpadOffset): update(request.op, scratchpad, request.wdata); - fromOffset(fan0StateOffset): update(request.op, fans[0].state, request.wdata); - fromOffset(fan1StateOffset): update(request.op, fans[1].state, request.wdata); - fromOffset(fan2StateOffset): update(request.op, fans[2].state, request.wdata); - fromOffset(fan3StateOffset): update(request.op, fans[3].state, request.wdata); - fromOffset(frontIoStateOffset): update(request.op, front_io_hsc, request.wdata); + rule do_page0_upate (maybe_op matches tagged Valid .op &&& op != READ); + case (address) + fromOffset(cs0Offset): update(op, checksum[0], wdata); + fromOffset(cs1Offset): update(op, checksum[1], wdata); + fromOffset(cs2Offset): update(op, checksum[2], wdata); + fromOffset(cs3Offset): update(op, checksum[3], wdata); + fromOffset(scratchpadOffset): update(op, scratchpad, wdata); + fromOffset(fan0StateOffset): update(op, fans[0].state, wdata); + fromOffset(fan1StateOffset): update(op, fans[1].state, wdata); + fromOffset(fan2StateOffset): update(op, fans[2].state, wdata); + fromOffset(fan3StateOffset): update(op, fans[3].state, wdata); + fromOffset(frontIoStateOffset): update(op, front_io_hsc, wdata); endcase - page_request[0] <= tagged Invalid; + maybe_op <= tagged Invalid; endrule - // - // Page 1, Tofino sequencer, PCIe endpoint - // + method ActionValue#(Bool) offer(SpiRequest request) if (!isValid(maybe_op)); + let accepted = (request.address[15:8] == 0 && request.op != NOOP); + + if (accepted) begin + maybe_op <= tagged Valid request.op; + address <= request.address[7:0]; + wdata <= request.wdata; + end + + return accepted; + endmethod + + method read = response; +endmodule + +// +// Page 1, Tofino sequencer, PCIe endpoint +// + +module mkPage1 #( + Tofino2Sequencer::Registers tofino, + PCIeEndpointController::Registers pcie) + (RegisterPage); + Reg#(SpiResponse) response <- mkConfigRegU(); + Reg#(Maybe#(RegOps)) maybe_op <- mkReg(tagged Invalid); + Reg#(Bit#(8)) address <- mkRegU(); + Reg#(Bit#(8)) wdata <- mkRegU(); (* fire_when_enabled *) - rule do_page1_read (read_page(1) matches tagged Valid .request); + rule do_page1_read (maybe_op matches tagged Valid .op &&& op == READ); let reader = - case (request.address) + case (address) // Tofino sequencer fromOffset(tofinoSeqCtrlOffset): read(tofino.ctrl); fromOffset(tofinoSeqStateOffset): read(tofino.state); @@ -231,183 +252,250 @@ module mkSpiServer #( fromOffset(tofinoResetOffset): read(tofino.tofino_reset); fromOffset(tofinoMiscOffset): read(tofino.misc); - // PCIe + // PCIe control fromOffset(pcieHotplugCtrlOffset): read(pcie.ctrl); fromOffset(pcieHotplugStatusOffset): read(pcie.status); default: read(8'h00); endcase; - let data <- reader; + let rdata <- reader; - page_request[1] <= tagged Invalid; - page_response[1] <= data; + maybe_op <= tagged Invalid; + response <= rdata; endrule (* fire_when_enabled *) - rule do_page1_update (update_page(1) matches tagged Valid .request); - case (request.address) - fromOffset(tofinoSeqCtrlOffset): update(request.op, tofino.ctrl, request.wdata); - fromOffset(pcieHotplugCtrlOffset): update(request.op, pcie.ctrl, request.wdata); + rule do_page1_update (maybe_op matches tagged Valid .op &&& op != READ); + case (address) + fromOffset(tofinoSeqCtrlOffset): update(op, tofino.ctrl, wdata); + fromOffset(pcieHotplugCtrlOffset): update(op, pcie.ctrl, wdata); endcase - page_request[1] <= tagged Invalid; + maybe_op <= tagged Invalid; endrule - // - // Page 2, Tofino Debug Port - // + method ActionValue#(Bool) offer(SpiRequest request) if (!isValid(maybe_op)); + let accepted = (request.address[15:8] == 1 && request.op != NOOP); + + if (accepted) begin + maybe_op <= tagged Valid request.op; + address <= request.address[7:0]; + wdata <= request.wdata; + end + + return accepted; + endmethod + + method read = response; +endmodule + +// +// Page 2, Tofino Debug Port +// + +module mkPage2 #(TofinoDebugPort::Registers tofino_debug_port) (RegisterPage); + Reg#(SpiResponse) response <- mkConfigRegU(); + Reg#(Maybe#(RegOps)) maybe_op <- mkReg(tagged Invalid); + Reg#(Bit#(8)) address <- mkRegU(); + Reg#(Bit#(8)) wdata <- mkRegU(); (* fire_when_enabled *) - rule do_page2_read (read_page(2) matches tagged Valid .request); + rule do_page2_read (maybe_op matches tagged Valid .op &&& op == READ); let reader = - case (request.address) - fromOffset(tofinoDebugPortStateOffset): read(tofino_debug_port.state); - fromOffset(tofinoDebugPortBufferOffset): read_volatile(tofino_debug_port.buffer); + case (address) + fromOffset(tofinoDebugPortStateOffset): + read(tofino_debug_port.state); + fromOffset(tofinoDebugPortBufferOffset): + read_volatile(tofino_debug_port.buffer); default: read(8'h00); endcase; - let data <- reader; + let rdata <- reader; - page_request[2] <= tagged Invalid; - page_response[2] <= data; + maybe_op <= tagged Invalid; + response <= rdata; endrule (* fire_when_enabled *) - rule do_page2_update (update_page(2) matches tagged Valid .request); - case (request.address) + rule do_page2_update (maybe_op matches tagged Valid .op &&& op != READ); + case (address) fromOffset(tofinoDebugPortStateOffset): - update(request.op, tofino_debug_port.state, request.wdata); + update(op, tofino_debug_port.state, wdata); fromOffset(tofinoDebugPortBufferOffset): - write(WRITE, tofino_debug_port.buffer._write, request.wdata); + write(WRITE, tofino_debug_port.buffer._write, wdata); endcase - page_request[2] <= tagged Invalid; + maybe_op <= tagged Invalid; endrule - // - // Ignition Pages - // + method ActionValue#(Bool) offer(SpiRequest request) if (!isValid(maybe_op)); + let accepted = (request.address[15:8] == 2 && request.op != NOOP); - ConfigReg#(Vector#(5, Bit#(8))) target_present_summary <- mkConfigRegU(); + if (accepted) begin + maybe_op <= tagged Valid request.op; + address <= request.address[7:0]; + wdata <= request.wdata; + end - (* fire_when_enabled *) - rule do_demux_target_present_summary; - // Collect a summary of the Target present bits as a 64 bit-vector. - function target_present(registers) = - registers.controller_state.target_present; + return accepted; + endmethod - target_present_summary <= - unpack(extend(pack(map(target_present, ignition_pages)))); - endrule + method read = response; +endmodule + +module mkIgnitionGeneralPage + #(IgnitionController::Controller#(n_ignition_controllers) ignition) + (RegisterPage) + provisos (Add#(n_ignition_controllers, a__, 64)); + Reg#(Maybe#(Bit#(8))) maybe_address <- mkReg(tagged Invalid); + Reg#(SpiResponse) response <- mkConfigRegU(); + + Vector#(8, Bit#(8)) presence_summary = + unpack(extend(pack(ignition.presence_summary))); (* fire_when_enabled *) - rule do_ignition_summary_page_read ( - read_page(3) matches tagged Valid .request); + rule do_get_response (maybe_address matches tagged Valid .address); let reader = - case (request.address) + case (address) fromOffset(ignitionControllersCountOffset): - read(Bit#(8)'(fromInteger(valueOf(n_ignition_controllers)))); - fromOffset(ignitionTargetsPresent0Offset): read(target_present_summary[0]); - fromOffset(ignitionTargetsPresent1Offset): read(target_present_summary[1]); - fromOffset(ignitionTargetsPresent2Offset): read(target_present_summary[2]); - fromOffset(ignitionTargetsPresent3Offset): read(target_present_summary[3]); - fromOffset(ignitionTargetsPresent4Offset): read(target_present_summary[4]); + read(Bit#(8)'(fromInteger( + valueOf(n_ignition_controllers)))); + fromOffset(ignitionTargetsPresent0Offset): + read(presence_summary[0]); + fromOffset(ignitionTargetsPresent1Offset): + read(presence_summary[1]); + fromOffset(ignitionTargetsPresent2Offset): + read(presence_summary[2]); + fromOffset(ignitionTargetsPresent3Offset): + read(presence_summary[3]); + fromOffset(ignitionTargetsPresent4Offset): + read(presence_summary[4]); default: read(8'h00); endcase; - let data <- reader; + let rdata <- reader; + + maybe_address <= tagged Invalid; + response <= rdata; + endrule + + method ActionValue#(Bool) offer(SpiRequest request) + if (!isValid(maybe_address)); + let accepted = (request.address[15:8] == 3 && request.op != NOOP); + + if (accepted) begin + maybe_address <= tagged Valid request.address[7:0]; + end + + return accepted; + endmethod + + method read = response; +endmodule + +module mkIgnitionRegistersPage #(RegisterServer#(n) registers) (RegisterPage) + provisos (Add#(TLog#(n), a__, 6)); + Reg#(Bool) await_response <- mkReg(False); + Reg#(SpiResponse) response <- mkConfigRegU(); + + (* fire_when_enabled *) + rule do_get_response (await_response); + let data <- registers.response.get; - page_request[3] <= tagged Invalid; - page_response[3] <= data; + await_response <= False; + response <= SpiResponse {readdata: data}; endrule - for (Integer i = 0; i < valueOf(n_ignition_controllers); i = i + 1) begin - let page_id = i + 4; - let registers = asIfc(ignition_pages[i]); + method ActionValue#(Bool) offer(SpiRequest request) if (!await_response); + let accepted = ({request.address[15:14], request.address[7]} == 3'b010); - (* fire_when_enabled *) - rule do_ignition_page_read ( - read_page(page_id) matches tagged Valid ._request); - let reader = - case (_request.address) - // Controller state + ControllerId#(n) controller = unpack(truncate(request.address[13:8])); + + if (accepted) begin + // Determine if the request is for a live register. + let maybe_register = + case (request.address[7:0]) + fromInteger(transceiverStateOffset): + tagged Valid TransceiverState; fromInteger(controllerStateOffset): - read(registers.controller_state); - fromInteger(controllerLinkStatusOffset): - read(registers.controller_link_status); + tagged Valid ControllerState; fromInteger(targetSystemTypeOffset): - read(registers.target_system_type); + tagged Valid TargetSystemType; fromInteger(targetSystemStatusOffset): - read(registers.target_system_status); - fromInteger(targetSystemFaultsOffset): - read(registers.target_system_faults); - fromInteger(targetRequestStatusOffset): - read(registers.target_request_status); + tagged Valid TargetSystemStatus; + fromInteger(targetSystemEventsOffset): + tagged Valid TargetSystemEvents; + fromInteger(targetSystemPowerRequestStatusOffset): + tagged Valid TargetSystemPowerRequestStatus; fromInteger(targetLink0StatusOffset): - read(registers.target_link0_status); + tagged Valid TargetLink0Status; fromInteger(targetLink1StatusOffset): - read(registers.target_link1_status); - - // Target Request - fromInteger(targetRequestOffset): - read(registers.target_request); - - // Controller Counters - fromInteger(controllerStatusReceivedCountOffset): - read_volatile(registers.controller_status_received_count); - fromInteger(controllerHelloSentCountOffset): - read_volatile(registers.controller_hello_sent_count); - fromInteger(controllerRequestSentCountOffset): - read_volatile(registers.controller_request_sent_count); - fromInteger(controllerMessageDroppedCountOffset): - read_volatile(registers.controller_message_dropped_count); - - // Controller Link Events - fromInteger(controllerLinkEventsSummaryOffset): - read(registers.controller_link_counters.summary); - - // Target Link 0 Events - fromInteger(targetLink0EventsSummaryOffset): - read(registers.target_link0_counters.summary); - - // Target Link 1 Events - fromInteger(targetLink1EventsSummaryOffset): - read(registers.target_link1_counters.summary); - - default: read(8'h00); + tagged Valid TargetLink1Status; + default: + tagged Invalid; endcase; - let data <- reader; - - page_request[page_id] <= tagged Invalid; - page_response[page_id] <= data; - endrule - - (* fire_when_enabled *) - rule do_ignition_page_update ( - update_page(page_id) matches tagged Valid ._request); - function do_update(r) = update(_request.op, r, _request.wdata); - - case (_request.address) - fromInteger(controllerStateOffset): - do_update(registers.controller_state); - fromInteger(targetRequestOffset): - do_update(registers.target_request); - fromInteger(controllerLinkEventsSummaryOffset): - do_update(registers.controller_link_counters.summary); - fromInteger(targetLink0EventsSummaryOffset): - do_update(registers.target_link0_counters.summary); - fromInteger(targetLink1EventsSummaryOffset): - do_update(registers.target_link1_counters.summary); - endcase - - page_request[page_id] <= tagged Invalid; - endrule - end - - interface Put request = toPut(asIfc(in)); - interface Put response = toGet(asIfc(out)); + if (maybe_register matches tagged Valid .register &&& + request.op == READ) begin + await_response <= True; + registers.request.put( + RegisterRequest { + id: controller, + register: register, + op: tagged Read}); + end + else if + (maybe_register matches tagged Valid .register &&& + request.op == WRITE) begin + registers.request.put( + RegisterRequest { + id: controller, + register: register, + op: tagged Write request.wdata}); + end + else begin + response <= SpiResponse {readdata: 'h00}; + end + end + + return accepted; + endmethod + + method read = response; +endmodule + +module mkIgnitionCountersPage #(CounterServer#(n) counters) (RegisterPage) + provisos (Add#(TLog#(n), a__, 6)); + Reg#(Bool) await_response <- mkReg(False); + Reg#(SpiResponse) response <- mkConfigRegU(); + + (* fire_when_enabled *) + rule do_get_response (await_response); + let data <- counters.response.get; + + await_response <= False; + response <= SpiResponse {readdata: pack(data)}; + endrule + + method ActionValue#(Bool) offer(SpiRequest request) if (!await_response); + let accepted = ({request.address[15:14], request.address[7]} == 3'b011); + + ControllerId#(n) controller = unpack(truncate(request.address[13:8])); + CounterId counter = unpack(request.address[5:0]); + + if (accepted) begin + await_response <= True; + counters.request.put( + CounterAddress { + controller: controller, + counter: counter}); + end + + return accepted; + endmethod + + method read = response; endmodule // @@ -418,9 +506,9 @@ typedef RegRequest#(16, 8) SpiRequest; typedef RegResp#(8) SpiResponse; typedef Server#(SpiRequest, SpiResponse) SpiServer; -function UInt#(8) fromOffset(Integer offset); +function Bit#(8) fromOffset(Integer offset); Bit#(16) offset_ = fromInteger(offset); - return unpack(offset_[7:0]); + return offset_[7:0]; endfunction // Turn the read of a register into an ActionValue. @@ -459,4 +547,6 @@ function Action write(RegOps op, function Action f(t val), Bit#(8) data) endaction; endfunction + + endpackage diff --git a/hdl/projects/sidecar/mainboard/SidecarMainboardControllerTop.bsv b/hdl/projects/sidecar/mainboard/SidecarMainboardControllerTop.bsv index cbf40233..7bfb651f 100644 --- a/hdl/projects/sidecar/mainboard/SidecarMainboardControllerTop.bsv +++ b/hdl/projects/sidecar/mainboard/SidecarMainboardControllerTop.bsv @@ -307,32 +307,18 @@ typedef SampledSerialIOTxInout#(5) IgnitionIO; module mkIgnitionIOs #( Integer bank_id, - Vector#(n, IgnitionController::Controller) controllers) + Vector#(n, Tuple2#(Bool, GetPut#(Bit#(1)))) txrs) (Vector#(n, IgnitionIO)); // The modulo 5 causes the strobe instances for different banks to be offset // in phase. This avoids all transceivers switching at once and instead // spreads out the transmit activity. Strobe#(3) tx_strobe <- mkLimitStrobe(1, 5, fromInteger(bank_id % 5)); - function to_serial(txr) = txr.serial; - - Transceivers#(n) txrs <- mkTransceivers(); - Vector#(n, IgnitionIO) io <- zipWithM( - mkSampledSerialIOWithTxStrobeInout(tx_strobe), - map(tx_enabled, controllers), - map(to_serial, txrs.txrs)); - mkFreeRunningStrobe(tx_strobe); - zipWithM(mkConnection, map(transceiver_client, controllers), txrs.txrs); - // Create a registered copy of the first Controller tick to help P&R. - Reg#(Bool) watchdog_tick <- mkRegU(); - - (* fire_when_enabled *) - rule do_receiver_watchdog; - watchdog_tick <= controllers[0].tick_1khz; - if (watchdog_tick) txrs.tick_1khz(); - endrule + Vector#(n, IgnitionIO) io <- mapM( + uncurry(mkSampledSerialIOWithTxStrobeInout(tx_strobe)), + txrs); return io; endmodule @@ -366,10 +352,10 @@ module mkSidecarMainboardControllerTop controller.registers.tofino, controller.registers.tofino_debug_port, controller.registers.pcie, - register_pages(controller.ignition_controllers), + controller.ignition_controller, controller.registers.fans, - asIfc(controller.registers.front_io_hsc), - reset_by reset_sync); + controller.registers.front_io_hsc, + reset_by reset_sync); InputReg#(Bit#(1), 2) csn <- mkInputSyncFor(spi_phy.pins.csn); InputReg#(Bit#(1), 2) sclk <- mkInputSyncFor(spi_phy.pins.sclk); @@ -529,60 +515,76 @@ module mkSidecarMainboardControllerTop // to maintain a logical channel order in the register interface. See // further down in this module how the transceiver channels map to device // pins. + // + + ControllerTransceiver#(36) + ignition_txr <- mkControllerTransceiver36(reset_by reset_sync); + + mkConnection(ignition_txr, controller.ignition_controller.txr); + + // Connect the transceiver watchdog timer. + (* fire_when_enabled *) + rule do_ignition_txr_watchdog (controller.ignition_tick_1khz); + ignition_txr.tick_1khz(); + endrule + // // Cubbies // - Vector#(8, IgnitionController::Controller) + + Vector#(8, Tuple2#(Bool, GetPut#(Bit#(1)))) ignition_bank2 = vec( - controller.ignition_controllers[15], - controller.ignition_controllers[14], - controller.ignition_controllers[13], - controller.ignition_controllers[12], - controller.ignition_controllers[11], - controller.ignition_controllers[10], - controller.ignition_controllers[9], - controller.ignition_controllers[19]); - Vector#(6, IgnitionController::Controller) + ignition_txr.serial[15], + ignition_txr.serial[14], + ignition_txr.serial[13], + ignition_txr.serial[12], + ignition_txr.serial[11], + ignition_txr.serial[10], + ignition_txr.serial[9], + ignition_txr.serial[19]); + Vector#(6, Tuple2#(Bool, GetPut#(Bit#(1)))) ignition_bank3_0 = vec( - controller.ignition_controllers[18], - controller.ignition_controllers[17], - controller.ignition_controllers[16], - controller.ignition_controllers[8], - controller.ignition_controllers[4], - controller.ignition_controllers[7]); - Vector#(6, IgnitionController::Controller) + ignition_txr.serial[18], + ignition_txr.serial[17], + ignition_txr.serial[16], + ignition_txr.serial[8], + ignition_txr.serial[4], + ignition_txr.serial[7]); + Vector#(6, Tuple2#(Bool, GetPut#(Bit#(1)))) ignition_bank3_1 = vec( - controller.ignition_controllers[1], - controller.ignition_controllers[0], - controller.ignition_controllers[6], - controller.ignition_controllers[5], - controller.ignition_controllers[3], - controller.ignition_controllers[2]); - Vector#(6, IgnitionController::Controller) + ignition_txr.serial[1], + ignition_txr.serial[0], + ignition_txr.serial[6], + ignition_txr.serial[5], + ignition_txr.serial[3], + ignition_txr.serial[2]); + Vector#(6, Tuple2#(Bool, GetPut#(Bit#(1)))) ignition_bank6_0 = vec( - controller.ignition_controllers[20], - controller.ignition_controllers[21], - controller.ignition_controllers[22], - controller.ignition_controllers[23], - controller.ignition_controllers[24], - controller.ignition_controllers[25]); - Vector#(6, IgnitionController::Controller) + ignition_txr.serial[20], + ignition_txr.serial[21], + ignition_txr.serial[22], + ignition_txr.serial[23], + ignition_txr.serial[24], + ignition_txr.serial[25]); + Vector#(6, Tuple2#(Bool, GetPut#(Bit#(1)))) ignition_bank6_1 = vec( - controller.ignition_controllers[30], - controller.ignition_controllers[31], - controller.ignition_controllers[27], - controller.ignition_controllers[26], - controller.ignition_controllers[28], - controller.ignition_controllers[29]); + ignition_txr.serial[30], + ignition_txr.serial[31], + ignition_txr.serial[27], + ignition_txr.serial[26], + ignition_txr.serial[28], + ignition_txr.serial[29]); + // // PSC 0/1, Sidecar B, local Target // - Vector#(4, IgnitionController::Controller) + + Vector#(4, Tuple2#(Bool, GetPut#(Bit#(1)))) ignition_bank7 = vec( - controller.ignition_controllers[32], - controller.ignition_controllers[33], - controller.ignition_controllers[34], - controller.ignition_controllers[35]); + ignition_txr.serial[32], + ignition_txr.serial[33], + ignition_txr.serial[34], + ignition_txr.serial[35]); // Allocate the Transceivers and IO adapters for the banks of Controllers. // The bank id passed to `mkIgnitionIOs(..)` is used to derive a TX strobe diff --git a/hdl/projects/sidecar/mainboard/TofinoDebugPort.bsv b/hdl/projects/sidecar/mainboard/TofinoDebugPort.bsv index a8be7c36..7a6a724f 100644 --- a/hdl/projects/sidecar/mainboard/TofinoDebugPort.bsv +++ b/hdl/projects/sidecar/mainboard/TofinoDebugPort.bsv @@ -122,8 +122,8 @@ module mkTofinoDebugPort #( ConfigReg#(Maybe#(Error)) error <- mkConfigReg(tagged Invalid); // Buffer and connections to the module interface. - FIFOF#(Bit#(8)) send_buffer <- mkSizedBRAMFIFOF(256); - FIFOF#(Bit#(8)) receive_buffer <- mkSizedBRAMFIFOF(256); + FIFOF#(Bit#(8)) send_buffer <- mkSizedBRAMFIFOF(255); + FIFOF#(Bit#(8)) receive_buffer <- mkSizedBRAMFIFOF(255); // Connect the FIFOs to the register interface. The use of a DWire between // the receive FIFO and register interface will return a 0 if the FIFO is diff --git a/hdl/projects/sidecar/mainboard/emulator/BUILD b/hdl/projects/sidecar/mainboard/emulator/BUILD index b20fdada..d8b36d1a 100644 --- a/hdl/projects/sidecar/mainboard/emulator/BUILD +++ b/hdl/projects/sidecar/mainboard/emulator/BUILD @@ -5,14 +5,14 @@ bluespec_library('SidecarMainboardEmulator', 'SidecarMainboardEmulator.bsv', ], deps = [ - '//hdl/ip/bsv:PowerRail', - '//hdl/projects/sidecar/mainboard:SidecarMainboardControllerTop', - '//hdl/ip/bsv/ignition:Target', - '//hdl/ip/bsv/ignition:Transceiver', '//hdl/ip/bsv:BitSampling', + '//hdl/ip/bsv:PowerRail', '//hdl/ip/bsv:SerialIO', '//hdl/ip/bsv:Strobe', + '//hdl/ip/bsv/ignition:Target', + '//hdl/ip/bsv/ignition:Transceiver', '//hdl/ip/bsv/interfaces:ECP5', + '//hdl/projects/sidecar/mainboard:SidecarMainboardControllerTop', ]) bluespec_verilog('mkSidecarMainboardEmulatorOnEcp5Evn', diff --git a/hdl/projects/sidecar/mainboard/emulator/SidecarMainboardEmulator.bsv b/hdl/projects/sidecar/mainboard/emulator/SidecarMainboardEmulator.bsv index 93f3cc44..4337f0fb 100644 --- a/hdl/projects/sidecar/mainboard/emulator/SidecarMainboardEmulator.bsv +++ b/hdl/projects/sidecar/mainboard/emulator/SidecarMainboardEmulator.bsv @@ -121,7 +121,7 @@ module mkSidecarMainboardEmulator (SidecarMainboardEmulatorTop) controller.registers.tofino, controller.registers.tofino_debug_port, controller.registers.pcie, - IgnitionController::register_pages(controller.ignition_controllers), + controller.ignition_controller, controller.registers.fans, controller.registers.front_io_hsc); @@ -138,10 +138,14 @@ module mkSidecarMainboardEmulator (SidecarMainboardEmulatorTop) // let limit_for_1khz = fromInteger(parameters.system_frequency_hz / 1000); + let limit_for_1mhz = fromInteger(parameters.system_frequency_hz / 1_000_000); Strobe#(20) tick_1khz <- mkLimitStrobe(1, limit_for_1khz, 0); mkFreeRunningStrobe(tick_1khz); + Strobe#(6) tick_1mhz <- mkLimitStrobe(1, limit_for_1mhz, 0); + mkFreeRunningStrobe(tick_1mhz); + // // PDN // @@ -181,26 +185,17 @@ module mkSidecarMainboardEmulator (SidecarMainboardEmulatorTop) // Instantiate Transceivers, IO adapters and connect them to the Ignition // Controllers. - Transceivers#(n_ignition_controllers) controller_txrs <- mkTransceivers(); - - zipWithM( - mkConnection, - controller_txrs.txrs, - map(transceiver_client, - controller.ignition_controllers)); + ControllerTransceiver#(4) controller_txrs <- mkControllerTransceiver4(); - mkConnection(asIfc(tick_1khz), asIfc(controller_txrs.tick_1khz)); + mkConnection(controller_txrs, controller.ignition_controller.txr); + //mkConnection(asIfc(tick_1khz), asIfc(controller_txrs.tick_1khz)); Strobe#(3) controller_tx_strobe <- mkLimitStrobe(1, 5, 0); mkFreeRunningStrobe(controller_tx_strobe); - function to_serial(txr) = txr.serial; - - Vector#(4, SampledSerialIOTxOutputEnable#(5)) controller_io <- - zipWithM( - mkSampledSerialIOWithTxStrobeAndOutputEnable(controller_tx_strobe), - map(tx_enabled, controller.ignition_controllers), - map(to_serial, controller_txrs.txrs)); + Vector#(4, SampledSerialIOTxOutputEnable#(5)) controller_io <- mapM( + uncurry(mkSampledSerialIOWithTxStrobeAndOutputEnable(controller_tx_strobe)), + controller_txrs.serial); // Make Inouts for the two adapters connecting to the external Targets, // allowing them to be connected to the top level. @@ -267,16 +262,16 @@ module mkSidecarMainboardEmulator (SidecarMainboardEmulatorTop) ReadOnly#(Bit#(1)) ignition_link2_status_led <- mkLinkStatusLED( - controller.ignition_controllers[2].status.target_present, - controller_txrs.txrs[2].status, - controller_txrs.txrs[2].receiver_locked_timeout, + controller.ignition_controller.presence_summary[2], + link_status_disconnected, //controller_txrs.txrs[2].status, + False, //controller_txrs.txrs[2].receiver_locked_timeout, False); ReadOnly#(Bit#(1)) ignition_link3_status_led <- mkLinkStatusLED( - controller.ignition_controllers[3].status.target_present, - controller_txrs.txrs[3].status, - controller_txrs.txrs[3].receiver_locked_timeout, + controller.ignition_controller.presence_summary[3], + link_status_disconnected, //controller_txrs.txrs[3].status, + False, //controller_txrs.txrs[3].receiver_locked_timeout, False); Reg#(Bit#(8)) led_r <- mkConfigRegU(); diff --git a/hdl/projects/sidecar/mainboard/test/BUILD b/hdl/projects/sidecar/mainboard/test/BUILD index 3677e7f4..9f4ec186 100644 --- a/hdl/projects/sidecar/mainboard/test/BUILD +++ b/hdl/projects/sidecar/mainboard/test/BUILD @@ -39,9 +39,9 @@ bluesim_tests('TofinoDebugPortTests', 'mkDirectWriteTest', ], deps = [ - '//hdl/projects/sidecar/mainboard:TofinoDebugPort', - '//hdl/ip/bsv/I2C:I2CPeripheralModel', '//hdl/ip/bsv:TestUtils', + '//hdl/ip/bsv/I2C:I2CPeripheralModel', + '//hdl/projects/sidecar/mainboard:TofinoDebugPort', ]) bluesim_tests('Tofino2SequencerTests', @@ -69,9 +69,9 @@ bluesim_tests('Tofino2SequencerTests', ], deps = [ '//hdl/ip/bsv:PowerRail', - '//hdl/projects/sidecar/mainboard:Tofino2Sequencer', '//hdl/ip/bsv:Strobe', '//hdl/ip/bsv:TestUtils', + '//hdl/projects/sidecar/mainboard:Tofino2Sequencer', ]) bluesim_tests('MainboardControllerTests', @@ -81,7 +81,27 @@ bluesim_tests('MainboardControllerTests', 'mkFrontIOHSCTest', ], deps = [ - '//hdl/ip/bsv:PowerRail', '//hdl/projects/sidecar/mainboard:SidecarMainboardController', + '//hdl/ip/bsv:PowerRail', '//hdl/ip/bsv:TestUtils', ]) + +bluesim_tests('SpiServerTests', + suite = 'SidecarMainboardControllerSpiServerTests.bsv', + env = '//bluesim_default', + modules = [ + 'mkReadIdTest', + 'mkReadIgnitionCounterTest', + 'mkReadIgnitionPortCountTest', + 'mkReadIgnitionPresenceSummaryTest', + 'mkReadIgnitionTargetStatusTest', + 'mkReadIgnitionTransceiverStateTest', + 'mkSetIgnitionTransmitterOutputEnableModeTest', + ], + deps = [ + '//hdl/ip/bsv:FanModule', + '//hdl/ip/bsv:PowerRail', + '//hdl/ip/bsv:TestUtils', + '//hdl/ip/bsv/ignition/test:TestHelpers', + '//hdl/projects/sidecar/mainboard:SidecarMainboardController', + ]) diff --git a/hdl/projects/sidecar/mainboard/test/SidecarMainboardControllerSpiServerTests.bsv b/hdl/projects/sidecar/mainboard/test/SidecarMainboardControllerSpiServerTests.bsv new file mode 100644 index 00000000..1fac6c2a --- /dev/null +++ b/hdl/projects/sidecar/mainboard/test/SidecarMainboardControllerSpiServerTests.bsv @@ -0,0 +1,289 @@ +package SidecarMainboardControllerSpiServerTests; + +import ClientServer::*; +import GetPut::*; +import StmtFSM::*; +import Vector::*; + +import RegCommon::*; +import TestUtils::*; + +import IgnitionController::*; +import IgnitionProtocol::*; +import IgnitionReceiver::*; +import IgnitionTestHelpers::*; +import IgnitionTransceiver::*; +import PCIeEndpointController::*; +import PowerRail::*; +import SidecarMainboardController::*; +import SidecarMainboardControllerReg::*; +import SidecarMainboardControllerSpiServer::*; +import SidecarMainboardMiscSequencers::*; +import Tofino2Sequencer::*; +import TofinoDebugPort::*; + +module mkReadIdTest (Empty); + match {.spi, .*} <- mkSpiServerBench(); + + mkAutoFSM(seq + assert_read_of_eq(spi, 'h0000, Id0'(defaultValue), "expected Id0"); + assert_read_of_eq(spi, 'h0001, Id1'(defaultValue), "expected Id1"); + assert_read_of_eq(spi, 'h0002, Id2'(defaultValue), "expected Id2"); + assert_read_of_eq(spi, 'h0003, Id3'(defaultValue), "expected Id3"); + endseq); + + mkTestWatchdog(50); +endmodule + +module mkReadIgnitionPortCountTest (Empty); + match {.spi, .*} <- mkSpiServerBench(); + + mkAutoFSM(seq + assert_read_of_eq(spi, 'h0300, 8'h2, "expected a port count of 2"); + endseq); + + mkTestWatchdog(50); +endmodule + +module mkReadIgnitionPresenceSummaryTest (Empty); + match {.spi, .ignition} <- mkSpiServerBench(); + + mkAutoFSM(seq + // Read the presence vector, asserting that none of the present bits are + // set. + await(ignition.idle); + assert_read_of_eq(spi, 'h0301, 8'h0, "expected no Target present"); + assert_read_of_eq(spi, 'h0302, 8'h0, "expected no Target present"); + assert_read_of_eq(spi, 'h0303, 8'h0, "expected no Target present"); + assert_read_of_eq(spi, 'h0304, 8'h0, "expected no Target present"); + assert_read_of_eq(spi, 'h0305, 8'h0, "expected no Target present"); + + controller_receive_status_message( + ignition, 0, + message_status_system_powered_on_link0_connected); + + await(ignition.idle); + assert_read_of_eq(spi, 'h0301, 8'h1, "expected Target 0 present"); + assert_read_of_eq(spi, 'h0302, 8'h0, "expected no Target present"); + assert_read_of_eq(spi, 'h0303, 8'h0, "expected no Target present"); + assert_read_of_eq(spi, 'h0304, 8'h0, "expected no Target present"); + assert_read_of_eq(spi, 'h0305, 8'h0, "expected no Target present"); + endseq); + + mkTestWatchdog(200); +endmodule + +module mkReadIgnitionTransceiverStateTest (Empty); + match {.spi, .ignition} <- mkSpiServerBench(); + + mkAutoFSM(seq + await(ignition.idle); // Complete init. + assert_read_of_eq(spi, + 'h4000, link_status_none, + "expected receiver not aligned or locked"); + + ignition.txr.rx.put(ReceiverEvent { + id: 0, + ev: tagged ReceiverStatusChange link_status_aligned}); + + await(ignition.idle); + assert_read_of_eq(spi, + 'h4000, link_status_aligned, + "expected receiver aligned"); + + ignition.txr.rx.put(ReceiverEvent { + id: 0, + ev: tagged ReceiverStatusChange link_status_locked}); + + await(ignition.idle); + assert_read_of_eq(spi, + 'h4000, link_status_locked, + "expected receiver locked"); + + ignition.txr.rx.put(ReceiverEvent { + id: 0, + ev: tagged ReceiverStatusChange link_status_polarity_inverted}); + + await(ignition.idle); + assert_read_of_eq(spi, + 'h4000, link_status_polarity_inverted, + "expected receiver polarity inverted"); + + ignition.txr.rx.put(ReceiverEvent { + id: 0, + ev: tagged ReceiverReset}); + + await(ignition.idle); + assert_read_of_eq(spi, + 'h4000, link_status_none, + "expected receiver not aligned or locked"); + endseq); + + mkTestWatchdog(200); +endmodule + +module mkSetIgnitionTransmitterOutputEnableModeTest (Empty); + match {.spi, .ignition} <- mkSpiServerBench(); + + let transmitter_disabled = + transceiver_state_value(Disabled, False, link_status_none); + let transmitter_enabled = + transceiver_state_value(AlwaysEnabled, True, link_status_none); + + function set_transmitter_state(value) = + spi.request.put(SpiRequest { + op: WRITE, + address: 'h4000, + wdata: value}); + + mkAutoFSM(seq + await(ignition.idle); // Complete init. + assert_read_of_eq(spi, + 'h4000, + transmitter_disabled, + "expected transmitter output enable mode"); + + set_transmitter_state(transmitter_enabled); + await(ignition.idle); + + assert_read_of_eq(spi, + 'h4000, + transmitter_enabled, + "expected transmitter output enable mode"); + + set_transmitter_state(transmitter_disabled); + await(ignition.idle); + + assert_read_of_eq(spi, + 'h4000, + transmitter_disabled, + "expected transmitter output enable mode"); + endseq); + + mkTestWatchdog(200); + endmodule + +module mkReadIgnitionTargetStatusTest (Empty); + match {.spi, .ignition} <- mkSpiServerBench(); + + mkAutoFSM(seq + await(ignition.idle); // Complete init. + + controller_receive_status_message( + ignition, 0, + message_status_system_powered_on_link0_connected); + await(ignition.idle); + + assert_read_of_eq(spi, + 'h4001, True, + "expected Target present"); + assert_read_of_eq(spi, + 'h4002, target_system_type, + "expected Target system type"); + assert_read_of_eq(spi, + 'h4003, system_status_system_power_enabled, + "expected Target system status power enabled"); + assert_read_of_eq(spi, + 'h4004, system_faults_none, + "expected no Target system faults"); + assert_read_of_eq(spi, + 'h4005, request_status_none, + "expected no Target system power requests in progress"); + assert_read_of_eq(spi, + 'h4006, link_status_connected, + "expected Target link 0 connected"); + assert_read_of_eq(spi, + 'h4007, link_status_disconnected, + "expected Target link 1 disconnected"); + endseq); + + mkTestWatchdog(200); +endmodule + +module mkReadIgnitionCounterTest (Empty); + match {.spi, .ignition} <- mkSpiServerBench(); + + function assert_counter_eq(address, expected_value, msg) = + assert_read_of_eq(spi, address, Counter'(expected_value), msg); + + mkAutoFSM(seq + await(ignition.idle); + // Clear counters because the counter RAM is not explicitly initialized. + read_and_discard(spi, 'h4080); + read_and_discard(spi, 'h4082); + + controller_receive_status_message( + ignition, 0, + message_status_system_powered_on_link0_connected); + await(ignition.idle); + + assert_counter_eq('h4080, 1, "expected Target present count 1"); + assert_counter_eq('h4080, 0, "expected Target present cleared"); + assert_counter_eq('h4082, 1, "expected Status received count 1"); + endseq); + + mkTestWatchdog(200); +endmodule + +module mkSpiServerBench + (Tuple2#(SpiServer, IgnitionController::Controller#(2))); + let parameters = SidecarMainboardController::Parameters'(defaultValue); + + Tofino2Sequencer tofino_sequencer <- mkTofino2Sequencer(defaultValue); + TofinoDebugPort tofino_debug_port <- mkTofinoDebugPort( + parameters.system_frequency_hz, + parameters.system_period_ns, + parameters.tofino_i2c_frequency_hz, + parameters.tofino_i2c_address, + parameters.tofino_i2c_stretch_timeout_us); + PCIeEndpointController pcie_endpoint <- + mkPCIeEndpointController(tofino_sequencer); + IgnitionController::Controller#(2) ignition <- + IgnitionController::mkController(defaultValue, True); + Vector#(4, FanModuleSequencer) fans <- replicateM(mkFanModuleSequencer); + PowerRail#(7) front_io_hsc <- + mkPowerRailDisableOnAbort(parameters.front_io_power_good_timeout); + + (* fire_when_enabled *) + rule do_tick_ignition; + ignition.tick_1mhz(); + endrule + + SpiServer spi <- mkSpiServer( + tofino_sequencer.registers, + tofino_debug_port.registers, + pcie_endpoint.registers, + ignition, + map(SidecarMainboardMiscSequencers::fan_registers, fans), + powerRailToReg(front_io_hsc)); + + return tuple2(spi, ignition); +endmodule + +function SpiResponse as_response(t data) + provisos (Bits#(t, t_sz), Add#(t_sz, _, 8)); + return unpack(extend(pack(data))); +endfunction + +function Stmt read_and_discard(SpiServer spi, Bit#(16) address); + return seq + spi.request.put(SpiRequest {op: READ, address: address, wdata: ?}); + assert_get_any(spi.response); + endseq; +endfunction + +function Stmt assert_read_of_eq( + SpiServer spi, + Bit#(16) address, + t expected_response, + String msg) + provisos ( + Bits#(t, t_sz), + Add#(t_sz, a__, 8)); + return seq + spi.request.put(SpiRequest {op: READ, address: address, wdata: ?}); + assert_get_bits_eq(spi.response, as_response(expected_response), msg); + endseq; +endfunction + +endpackage