diff --git a/litex/soc/cores/spi_opi.py b/litex/soc/cores/spi_opi.py index 1231fc8d2..4b02be083 100644 --- a/litex/soc/cores/spi_opi.py +++ b/litex/soc/cores/spi_opi.py @@ -14,56 +14,6 @@ from litex.soc.integration.doc import AutoDoc, ModuleDoc class S7SPIOPI(Module, AutoCSR, AutoDoc): - def add_timing_constraints(self, platform, padgroup_name): - # reminder to self: the {{ and }} overloading is because Python treats these as special in strings, so {{ -> { in actual constraint - # NOTE: ECSn is deliberately not constrained -- it's more or less async (0-10ns delay on the signal, only meant to line up with "block" region - - # constrain DQS-to-DQ input DDR delays - platform.add_platform_command("create_clock -name spidqs -period 10 [get_ports {}_dqs]".format(padgroup_name)) - platform.add_platform_command("set_input_delay -clock spidqs -max 0.6 [get_ports {{" + padgroup_name + "_dq[*]}}]") - platform.add_platform_command("set_input_delay -clock spidqs -min 4.4 [get_ports {{" + padgroup_name + "_dq[*]}}]") - platform.add_platform_command( - "set_input_delay -clock spidqs -max 0.6 [get_ports {{" + padgroup_name + "_dq[*]}}] -clock_fall -add_delay") - platform.add_platform_command( - "set_input_delay -clock spidqs -min 4.4 [get_ports {{" + padgroup_name + "_dq[*]}}] -clock_fall -add_delay") - - # derive clock for SCLK - clock-forwarded from DDR see Xilinx answer 62488 use case #4 - platform.add_platform_command( - "create_generated_clock -name spiclk_out -multiply_by 1 -source [get_pins {}/Q] [get_ports {}_sclk]".format( - self.sclk_name, padgroup_name)) - - # constrain CIPO SDR delay -- WARNING: -max is 'actually' 5.0ns, but design can't meet timing @ 5.0 tPD from SPIROM. There is some margin in the timing closure tho, so 4.5ns is probably going to work.... - platform.add_platform_command( - "set_input_delay -clock [get_clocks spiclk_out] -clock_fall -max 4.5 [get_ports {}_dq[1]]".format(padgroup_name)) - platform.add_platform_command( - "set_input_delay -clock [get_clocks spiclk_out] -clock_fall -min 1 [get_ports {}_dq[1]]".format(padgroup_name)) - # corresponding false path on CIPO DDR input when clocking SDR data - platform.add_platform_command( - "set_false_path -from [get_clocks spiclk_out] -to [get_pin {}/D ]".format(self.iddr_name + "1")) - # corresponding false path on CIPO SDR input from DQS strobe, only if the cipo path is used - if self.spiread: - platform.add_platform_command( - "set_false_path -from [get_clocks spidqs] -to [get_pin {}/D ]".format(self.cipo_name)) - - # constrain CLK-to-DQ output DDR delays; copi uses the same rules - platform.add_platform_command( - "set_output_delay -clock [get_clocks spiclk_out] -max 1 [get_ports {{" + padgroup_name + "_dq[*]}}]") - platform.add_platform_command( - "set_output_delay -clock [get_clocks spiclk_out] -min -1 [get_ports {{" + padgroup_name + "_dq[*]}}]") - platform.add_platform_command( - "set_output_delay -clock [get_clocks spiclk_out] -max 1 [get_ports {{" + padgroup_name + "_dq[*]}}] -clock_fall -add_delay") - platform.add_platform_command( - "set_output_delay -clock [get_clocks spiclk_out] -min -1 [get_ports {{" + padgroup_name + "_dq[*]}}] -clock_fall -add_delay") - # constrain CLK-to-CS output delay. NOTE: timings require one dummy cycle insertion between CS and SCLK (de)activations. Not possible to meet timing for DQ & single-cycle CS due to longer tS/tH reqs for CS - platform.add_platform_command( - "set_output_delay -clock [get_clocks spiclk_out] -min -1 [get_ports {}_cs_n]".format(padgroup_name)) # -3 in reality - platform.add_platform_command( - "set_output_delay -clock [get_clocks spiclk_out] -max 1 [get_ports {}_cs_n]".format(padgroup_name)) # 4.5 in reality - # unconstrain OE path - we have like 10+ dummy cycles to turn the bus on wr->rd, and 2+ cycles to turn on end of read - platform.add_platform_command("set_false_path -through [ get_pins {net}_reg/Q ]", net=self.dq.oe) - platform.add_platform_command("set_false_path -through [ get_pins {net}_reg/Q ]", - net=self.dq_copi.oe) - def __init__(self, pads, dq_delay_taps = 0, sclk_name = "SCLK_ODDR", @@ -507,9 +457,9 @@ class S7SPIOPI(Module, AutoCSR, AutoDoc): ]) self.wdata = CSRStorage(description="Page data to write to FLASH", fields = [ - CSRField("wdata", size=16, description="""16-bit wide write data presented to FLASH, committed to a 128-entry deep FIFO. - Writes to this register are not cached; note that writes to the SPINOR address space are also committed - to the FIFO, but this space is cached by the CPU, and therefore not guaranteed to be coherent or in order. + CSRField("wdata", size=16, description="""16-bit wide write data presented to FLASH, committed to a 128-entry deep FIFO. + Writes to this register are not cached; note that writes to the SPINOR address space are also committed + to the FIFO, but this space is cached by the CPU, and therefore not guaranteed to be coherent or in order. The direct wishbone-write address space is provisioned for e.g. USB bus masters that don't have caching.""") ] ) @@ -1468,3 +1418,53 @@ class S7SPIOPI(Module, AutoCSR, AutoDoc): ecc_reported.eq(0) ) ] + + def add_timing_constraints(self, platform, padgroup_name): + # reminder to self: the {{ and }} overloading is because Python treats these as special in strings, so {{ -> { in actual constraint + # NOTE: ECSn is deliberately not constrained -- it's more or less async (0-10ns delay on the signal, only meant to line up with "block" region + + # constrain DQS-to-DQ input DDR delays + platform.add_platform_command("create_clock -name spidqs -period 10 [get_ports {}_dqs]".format(padgroup_name)) + platform.add_platform_command("set_input_delay -clock spidqs -max 0.6 [get_ports {{" + padgroup_name + "_dq[*]}}]") + platform.add_platform_command("set_input_delay -clock spidqs -min 4.4 [get_ports {{" + padgroup_name + "_dq[*]}}]") + platform.add_platform_command( + "set_input_delay -clock spidqs -max 0.6 [get_ports {{" + padgroup_name + "_dq[*]}}] -clock_fall -add_delay") + platform.add_platform_command( + "set_input_delay -clock spidqs -min 4.4 [get_ports {{" + padgroup_name + "_dq[*]}}] -clock_fall -add_delay") + + # derive clock for SCLK - clock-forwarded from DDR see Xilinx answer 62488 use case #4 + platform.add_platform_command( + "create_generated_clock -name spiclk_out -multiply_by 1 -source [get_pins {}/Q] [get_ports {}_sclk]".format( + self.sclk_name, padgroup_name)) + + # constrain CIPO SDR delay -- WARNING: -max is 'actually' 5.0ns, but design can't meet timing @ 5.0 tPD from SPIROM. There is some margin in the timing closure tho, so 4.5ns is probably going to work.... + platform.add_platform_command( + "set_input_delay -clock [get_clocks spiclk_out] -clock_fall -max 4.5 [get_ports {}_dq[1]]".format(padgroup_name)) + platform.add_platform_command( + "set_input_delay -clock [get_clocks spiclk_out] -clock_fall -min 1 [get_ports {}_dq[1]]".format(padgroup_name)) + # corresponding false path on CIPO DDR input when clocking SDR data + platform.add_platform_command( + "set_false_path -from [get_clocks spiclk_out] -to [get_pin {}/D ]".format(self.iddr_name + "1")) + # corresponding false path on CIPO SDR input from DQS strobe, only if the cipo path is used + if self.spiread: + platform.add_platform_command( + "set_false_path -from [get_clocks spidqs] -to [get_pin {}/D ]".format(self.cipo_name)) + + # constrain CLK-to-DQ output DDR delays; copi uses the same rules + platform.add_platform_command( + "set_output_delay -clock [get_clocks spiclk_out] -max 1 [get_ports {{" + padgroup_name + "_dq[*]}}]") + platform.add_platform_command( + "set_output_delay -clock [get_clocks spiclk_out] -min -1 [get_ports {{" + padgroup_name + "_dq[*]}}]") + platform.add_platform_command( + "set_output_delay -clock [get_clocks spiclk_out] -max 1 [get_ports {{" + padgroup_name + "_dq[*]}}] -clock_fall -add_delay") + platform.add_platform_command( + "set_output_delay -clock [get_clocks spiclk_out] -min -1 [get_ports {{" + padgroup_name + "_dq[*]}}] -clock_fall -add_delay") + # constrain CLK-to-CS output delay. NOTE: timings require one dummy cycle insertion between CS and SCLK (de)activations. Not possible to meet timing for DQ & single-cycle CS due to longer tS/tH reqs for CS + platform.add_platform_command( + "set_output_delay -clock [get_clocks spiclk_out] -min -1 [get_ports {}_cs_n]".format(padgroup_name)) # -3 in reality + platform.add_platform_command( + "set_output_delay -clock [get_clocks spiclk_out] -max 1 [get_ports {}_cs_n]".format(padgroup_name)) # 4.5 in reality + # unconstrain OE path - we have like 10+ dummy cycles to turn the bus on wr->rd, and 2+ cycles to turn on end of read + platform.add_platform_command("set_false_path -through [ get_pins {net}_reg/Q ]", net=self.dq.oe) + platform.add_platform_command("set_false_path -through [ get_pins {net}_reg/Q ]", + net=self.dq_copi.oe)