From ffa1fe041406eed16ab7cc1367900653e1eb1296 Mon Sep 17 00:00:00 2001 From: WangXuan95 <629708558@qq.com> Date: Tue, 6 Jun 2023 19:52:19 +0800 Subject: [PATCH] change to Verilog2001 --- README.md | 945 +++++++++--------- RTL/{ddr_sdram_ctrl.sv => ddr_sdram_ctrl.v} | 173 ++-- ..._test_master.sv => axi_self_test_master.v} | 41 +- ...dram_model.sv => micron_ddr_sdram_model.v} | 0 ..._ddr_sdram_ctrl.sv => tb_ddr_sdram_ctrl.v} | 2 +- SIM/tb_ddr_sdram_ctrl_run_iverilog.bat | 2 +- example-selftest/SignalTap/stp1.stp | 6 +- ..._test_master.sv => axi_self_test_master.v} | 41 +- example-selftest/ddr_test.qsf | 58 +- example-selftest/{top.sv => fpga_top.v} | 6 +- example-uart-read-write/ddr_test.qsf | 15 +- .../{top.sv => fpga_top.v} | 21 +- example-uart-read-write/uart/uart2axi4.v | 295 ++++++ example-uart-read-write/uart/uart_rx.v | 335 +++++++ example-uart-read-write/uart/uart_tx.v | 340 +++++++ example-uart-read-write/uart2axi4.sv | 401 -------- figures/UartSession.png | Bin 22809 -> 8775 bytes 17 files changed, 1645 insertions(+), 1036 deletions(-) rename RTL/{ddr_sdram_ctrl.sv => ddr_sdram_ctrl.v} (81%) rename SIM/{axi_self_test_master.sv => axi_self_test_master.v} (79%) rename SIM/{micron_ddr_sdram_model.sv => micron_ddr_sdram_model.v} (100%) rename SIM/{tb_ddr_sdram_ctrl.sv => tb_ddr_sdram_ctrl.v} (96%) rename example-selftest/{axi_self_test_master.sv => axi_self_test_master.v} (79%) rename example-selftest/{top.sv => fpga_top.v} (97%) rename example-uart-read-write/{top.sv => fpga_top.v} (92%) create mode 100644 example-uart-read-write/uart/uart2axi4.v create mode 100644 example-uart-read-write/uart/uart_rx.v create mode 100644 example-uart-read-write/uart/uart_tx.v delete mode 100644 example-uart-read-write/uart2axi4.sv diff --git a/README.md b/README.md index 7e5d6a3..cc1a37d 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,462 @@ -![语言](https://img.shields.io/badge/语言-systemverilog_(IEEE1800_2005)-CAD09D.svg) ![仿真](https://img.shields.io/badge/仿真-iverilog-green.svg) ![部署](https://img.shields.io/badge/部署-quartus-blue.svg) ![部署](https://img.shields.io/badge/部署-vivado-FF1010.svg) +![语言](https://img.shields.io/badge/语言-verilog_(IEEE1364_2001)-9A90FD.svg) ![仿真](https://img.shields.io/badge/仿真-iverilog-green.svg) ![部署](https://img.shields.io/badge/部署-quartus-blue.svg) ![部署](https://img.shields.io/badge/部署-vivado-FF1010.svg) -中文 | [English](#en) +[English](#en) | [中文](#cn) -FPGA DDR-SDRAM +  + +FPGA DDR-SDRAM =========================== +DDR1-SDRAM controller with a AXI4 slave port. + +Replacing SDR-SDRAM with DDR-SDRAM (DDR1) in low-end FPGA designs. + +# Introduction + + |------------------------------| |-----------| + | | | | + ----->| driving clock and reset | | | + | | | | + ------------| | DDR-SDRAM interface |--------->| | + | AXI4 | | | | + AXI4 master |-------->| AXI4 slave | | | + | | | | | + ------------| |------------------------------| |-----------| + User Logic DDR1 Controller (ddr_sdram_ctrl.v) DDR1 chip + +Many low-end FPGA development boards use SDR-SDRAM as off-chip memory, but DDR-SDRAM (DDR1) is larger and less expensive than SDR-SDRAM. And like SDR-SDRAM, DDR1 can also be directly driven by common IO pins of low-end FPGAs. I write a soft core DDR1 controller with a slave AXI4 interface for user. The features of this controller are: + +* **Platform Independent** : Written in pure SystemVerilog and can run on various FPGAs including Altera and Xilinx. +* **Compatible** : Supports DDR1 of various bit widths and capacities (this has been verify by simulation on MICRON's DDR1 models). + +To demonstrate the use of this controller, I provide two demo projects: + +* **Self-test demo** : Through the DDR1 controller, write data into DDR1, then read it out, and compare whether the read data is consistent with the written data. +* **UART DDR1 read/write demo** : Convert UART commands into AXI4 bus actions to read and write DDR1 through the DDR1 controller. + +In addition, since the interface timing of each generation of DDR-SDRAM (such as DDR3, DDR2, DDR1) is similar, this repository can also facilitate those who are familiar with Verilog to learn the DDR-SDRAM interface. + +  + +# Table of Contents + +* [Hardware Design Guidelines](#Hardware Design Guidelines) +* [Hard Design Example](#Hard Design Example) +* [DDR1 Control Module Manual](#DDR1 Control Module Manual) + * [Module Parameters](#Module Parameters) + * [Module Interface: clock and reset](#Module Interface: clock and reset) + * [Module Interface: DDR1 interface](#Module Interface: DDR1 interface) + * [Module Interface: AXI4 slave](#Module Interface: AXI4 slave) + * [Bit Width Parameters](#Bit Width Parameters) + * [Timing Parameters](#Timing Parameters) +* [FPGA Demo Projects](#FPGA Demo Projects) + * [Self-test demo](#Self-test demo) + * [UART DDR1 read/write demo](#UART DDR1 read/write demo) +* [RTL Simulation](#RTL Simulation) + * [Modify Simulation Attributes](#Modify Simulation Attributes) + +  + +# Hardware Design Guidelines + +For FPGA selection, only an FPGA with a sufficient number of common IOs can drive DDR1. The IO level standard of DDR1 is often SSTL-2, which is compatible with 2.5V LVTTL or 2.5V LVCMOS, so the power supply of the corresponding FPGA IO bank should be 2.5V, and should be configured as 2.5V LVTTL or 2.5V LVCMOS in the FPGA development software . + +The following table shows the pin defination of the DDR1 chip and the points that should be paid attention to when connecting it to the FPGA. + +| pin name | direction | width | introduction | level | note | +| :----------------------------: | :---------: | :-----------------: | :-------------------------------- | :--------- | :------------------------------- | +| ddr_ck_p | FPGA output | 1 | DDR1 clock p , ≥75MHz | 2.5V LVTTL | differential route with ddr_ck_n | +| ddr_ck_n | FPGA output | 1 | DDR1 clock n , ≥75MHz | 2.5V LVTTL | differential route with ddr_ck_p | +| ddr_cke, ddr_cs_n | FPGA output | 1 | low speed | 2.5V LVTTL | | +| ddr_ras_n, ddr_cas_n, ddr_we_n | FPGA output | 1 | sync with ddr_ck_p's falling edge | 2.5V LVTTL | same length as ddr_ck_p | +| ddr_ba | FPGA output | 2 | sync with ddr_ck_p's falling edge | 2.5V LVTTL | same length as ddr_ck_p | +| ddr_a | FPGA output | depend on DDR1 part | sync with ddr_ck_p's falling edge | 2.5V LVTTL | same length as ddr_ck_p | +| ddr_dqs | inout | depend on DDR1 part | | 2.5V LVTTL | | +| ddr_dm、ddr_dq | inout | depend on DDR1 part | sync with ddr_dqs's double edge | 2.5V LVTTL | same length as ddr_dqs | + +  + +# Hard Design Example + +For demonstration, I used the cheapest FPGA of Altera Cyclone IV (model: EP4CE6E22C8N) and MICRON's 64MB DDR1 (model MT46V64M8TG-6T) to design a small board. All the demo project of this repository can run on this board directly. If you want to use DDR1 in your own PCB design, just refer to this board's design. + +| ![board-image](./figures/board.jpg) | +| :----------------------------------: | +| **Figure** : FPGA + DDR1 demo board. | + +See board schematic [PCB/sch.pdf](./PCB/sch.pdf) and manufacturing file [PCB/gerber.zip](./PCB/gerber.zip) . It is a double-layer board, and it is not necessary to pay attention to impedance matching like DDR2 and DDR3, because the operating frequency of the circuit is 75MHz on both sides, which is not particularly high. Just pay attention to keeping the distance between FPGA and DDR as close as possible, and the wiring as short as possible. For example, I put the DDR1 chip on the opposite side of the FPGA chip to keep the wiring short. + +The design of this board is open in LCEDA, see [oshwhub.com/wangxuan/fpga-ddr-ce-shi-ban](https://oshwhub.com/wangxuan/fpga-ddr-ce-shi-ban) . + +  + +# DDR1 Control Module Manual + +See [RTL/ddr_sdram_ctrl.v](./RTL/ddr_sdram_ctrl.v) for the DDR1 controller code, which can automatically initialize DDR1 and refresh it regularly. The module has a AXI4 slave interface through which reads and writes to DDR1 can be accomplished. This section introduce in detail how to use this module. + +## Module Parameters + +Verilog parameters of this module are defined as follows: + +```Verilog +module ddr_sdram_ctrl #( + parameter READ_BUFFER = 1, + parameter BA_BITS = 2, + parameter ROW_BITS = 13, + parameter COL_BITS = 11, + parameter DQ_LEVEL = 1, + parameter [9:0] tREFC = 10'd256, + parameter [7:0] tW2I = 8'd7, + parameter [7:0] tR2I = 8'd7 +) +``` + +These parameters are described in the table below: + +| Parameter | Type | value range | default value | introduction | +| :---------: | :----: | :---------: | :-----------: | :----------------------------------------------------------- | +| READ_BUFFER | | 0 or 1 | 1 | If it is set to 0, there will be no read data buffer in the DDR1 controller, and the read data will not wait for the AXI4 host to accept it, that is, the rvalid signal will not wait for the rready signal, and will be directly poured out at the highest rate. At this time, AXI4 is not Complete, but can reduce the latency of readout. If it is set to 1, there will be a large enough read data buffer in the DDR1 controller, and the rvalid signal will shake hands with the rready signal to confirm that the AXI4 host is ready before reading the data. | +| BA_BITS | Width | 1~4 | 2 | The width of DDR BANK ADDRESS (ddr_ba) is specified. Regular DDR1 BANK ADDRESS is 2bit, so this parameter is usually fixed to the default value and does not need to be changed. | +| ROW_BITS | Width | 1~15 | 13 | It specifies the width of the DDR ROW ADDRESS, and also determines the width of the address line pins (ddr_a) of the DDR1 chip. This parameter depends on the selection of the DDR1 chip. For example, MT46V64M8 has 8192 COLs per bank, considering 2^11=8192, this parameter should be 13. Similarly, for MT46V128M8, this parameter should be 14 | +| COL_BITS | Width | 1~14 | 11 | Specifies the width of the DDR COL ADDRESS. This parameter depends on the selection of the DDR1 chip. For example, MT46V64M8 has 2048 COLs per ROW. Considering 2^11=2048, this parameter should be 11. Similarly, for MT46V32M16, this parameter should be 10. | +| DQ_LEVEL | Width | 0~7 | 1 | The data bit width of the DDR1 chip is specified. For a chip with a bit width of x4 (such as MT46V64M4), this parameter should be 0; for a chip with a bit width of x8 (such as MT46V64M8), this parameter should be 1; for a chip with a bit width of x16 ( For example, MT46V64M16), this parameter should be 2; for the case of bit width x32 (such as two pieces of MT46V64M16 extended bit width), this parameter should be 3; and so on. | +| tREFC | Timing | 1\~1023 | 256 | The controller will refresh DDR1 periodically, and this parameter specifies the refresh interval of DDR1. See [Timing Parameters](#Timing Parameters) for details. | +| tW2I | Timing | 1\~255 | 7 | This parameter specifies the interval from the last write command of a write action to the activation command (ACT) of the next action. See [Timing Parameters](#Timing Parameters) for details. | +| tR2I | Timing | 1\~255 | 7 | This parameter specifies the interval from the last read command of a read action to the activation command (ACT) of the next action. See [Timing Parameters](#Timing Parameters) for details. | + +## Module Interface: clock and reset + +This module requires a drive clock and a drive reset, as follows: + +```Verilog + input wire rstn_async, + input wire drv_clk, +``` + +`rstn_async` is a low-level reset signal and should be set high during normal operation. `drv_clk` is the driving clock, and its frequency is 4 times the user clock. + +Before the module starts working, the `rstn_async` signal should be set low to reset the module, and then set rstn_async high to release the reset. + +### Clock freqency selection + +In this module, the driving clock `drv_clk` is divided by 4 to generate the DDR1 clock (`ddr_ck_p/ddr_ck_n`) and the AXI4 bus user clock (`clk`). This section describes how to determine the frequency of the drive clock `drv_clk`. + +First, the clock frequency is limited by the DDR1 chip. Considering that all DDR1 interface frequencies are at least 75MHz, the lower limit of `drv_clk` is 75\*4=300MHz. + +The upper limit of `drv_clk` also depends on the chip model of DDR1. For example, for MT46V64M8P-5B, check the chip manual, the maximum clock frequency of DDR1 with -5B suffix is 133MHz when CAS Latency (CL)=2, then the upper limit of `drv_clk` is 133 \*4=532MHz. + +> Note: This controller has a fixed CAS Latency (CL) = 2. + +In addition, the upper limit of the clock frequency is also limited by the speed of the FPGA. Too high a clock frequency can easily lead to timing failure. This design fully considers the timing safety design, most registers work in the clk clock domain with a lower frequency; some registers work in a clock that is twice the frequency of the clk clock, and the combinational logic of the input port is very short; Under the `drv_clk` of the frequency, but the input port comes directly from the register output of the upper stage (no combinational logic). Therefore, even on the EP4CE6E22C8N with a very low speed class, the correct operation of the module is guaranteed at a drive clock of 300MHz. + +## Module Interface: DDR1 interface + +Following is the DDR1 interface of this module. These signals should be pinout directly from the FPGA and connected to the DDR1 chip. + +```Verilog + output wire ddr_ck_p, ddr_ck_n, + output wire ddr_cke, + output reg ddr_cs_n, + output reg ddr_ras_n, + output reg ddr_cas_n, + output reg ddr_we_n, + output reg [ BA_BITS-1:0] ddr_ba, + output reg [ ROW_BITS-1:0] ddr_a, + output wire [((1< Note: When the module parameter `READ_BUFFER=0`, the module will save a BRAM resource and also reduce the delay between address channel handshake and data transfer. But the DDR1 controller ignores the `rready=0` condition and does not wait for the AXI4 host to be ready to accept data. This will destroy the completeness of the AXI4 protocol, but it may be useful in some simple situations. + + +## Bit Width Parameters + +This section describes how to determine the 4 parameters: `BA_BITS` , `ROW_BITS` , `COL_BITS` and `DQ_LEVEL`. + +Take [MICRON's DDR-SDRAM](https://www.micron.com/products/dram/ddr-sdram) series chips as an example, different chips have different ROW ADDRESS BITS, COL ADDRESS BITS and DATA BITS, which means that their bit width parameters are also different, as shown in the following table (note: these parameters can be found in the chip datasheet). + +| chip part | ddr_dq width | ddr_dm ddr_dqs width | DQ_LEVEL | BA_BITS | ROW_BITS | COL_BITS | byte per row | total capacity | awaddr/araddr width | +| :--------: | :----------: | :------------------: | :------: | :-----: | :------: | :------: | :----------: | :------------------------- | :-----------------: | +| MT46V64M4 | 4 | 1 | 0 | 2 | 13 | 11 | 1024 | 4\*2^(2+13+11)=256Mb=32MB | 25 | +| MT46V128M4 | 4 | 1 | 0 | 2 | 13 | 12 | 2048 | 4\*2^(2+13+12)=512Mb=64MB | 26 | +| MT46V256M4 | 4 | 1 | 0 | 2 | 14 | 12 | 4096 | 4\*2^(2+14+12)=1Gb=128MB | 27 | +| MT46V32M8 | 8 | 1 | 1 | 2 | 13 | 10 | 1024 | 8\*2^(2+13+10)=256Mb=32MB | 25 | +| MT46V64M8 | 8 | 1 | 1 | 2 | 13 | 11 | 2048 | 8\*2^(2+13+11)=512Mb=64MB | 26 | +| MT46V128M8 | 8 | 1 | 1 | 2 | 14 | 11 | 4096 | 8\*2^(2+14+11)=1Gb=128MB | 27 | +| MT46V16M16 | 16 | 2 | 2 | 2 | 13 | 9 | 1024 | 16\*2^(2+13+9)=256Mb=32MB | 25 | +| MT46V32M16 | 16 | 2 | 2 | 2 | 13 | 10 | 2048 | 16\*2^(2+13+10)=512Mb=64MB | 26 | +| MT46V64M16 | 16 | 2 | 2 | 2 | 14 | 10 | 4096 | 16\*2^(2+14+10)=1Gb=128MB | 27 | + +## Timing Parameters + +This section describes how to determine the 3 timing parameters `tREFC`, `tW2I` and `tR2I`. + +We know that DDR1 requires periodic refresh action, and `tREFC` specifies the refresh clock cycle interval (subject to clk). For example, if the user clock is 75MHz, according to the chip manual of MT46V64M8, it needs to be refreshed once at most 7.8125us. Considering that 75MHz * 7.8125us = 585.9, this parameter can be set to a value less than 585, such as `10'd512`. + +`tW2I` specifies the minimum number of clock cycles (in clk) from the last write command of a write operation to the active command (ACT) of the next operation. The following figure shows a write operation on a DDR1 interface. The first rising edge of ddr_cas_n represents the end of the last write command of a write operation, and the second falling edge of ddr_ras_n represents the next operation (may be read, write, Refresh), there are 5 clock cycles between them, `tW2I` is used to specify the lower limit of the number of cycles. The default value of `tW2I` is `8'd7`, which is a conservative value compatible with most DDR1s. For different DDR1 chips, there are different shrinkage margins (see DDR1 chip datasheet for details). + + __ __ __ __ __ __ __ __ __ __ __ __ + ddr_ck_p __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__ + _____ _______________________________________________ ________ + ddr_ras_n \_____/ \_____/ + _________________ ____________________________________________ + ddr_cas_n \___________/ + _________________ ____________________________________________ + ddr_we_n \___________/ + _____ + ddr_a[10] XXXXXXXXXXXXXXXXXX_____/ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + _______________________ + ddr_ba XXXXXX__________BA___________XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + _______________________ + ddr_a XXXXXX__RA_XXXXXXX_CA0_X_CA1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +`tR2I` specifies the minimum number of clock cycles (in `clk`) from the last read command of a read operation to the active command (ACT) of the next operation. The figure below shows a read operation on a DDR1 interface. The first rising edge of ddr_cas_n represents the end of the last read command of a read operation, and the second falling edge of ddr_ras_n represents the next operation (may be read, write, Refresh), there are 5 clock cycles between them, `tR2I` is used to specify the lower limit of the number of cycles. The default value of `tR2I` is `8'd7`, which is a conservative value compatible with most DDR1. For different DDR1 chips, there are different shrinkage margins (see DDR1 chip datasheet for details). + + __ __ __ __ __ __ __ __ __ __ __ __ + ddr_ck_p __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__ + _____ _______________________________________________ ________ + ddr_ras_n \_____/ \_____/ + _________________ ____________________________________________ + ddr_cas_n \___________/ + __________________________________________________________________________ + ddr_we_n + _____ + ddr_a[10] XXXXXXXXXXXXXXXXXX_____/ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + _______________________ + ddr_ba XXXXXX__________BA___________XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + _______________________ + ddr_a XXXXXX__RA_XXXXXXX_CA0_X_CA1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +  + +# FPGA Demo Projects + +## Self-test demo + +I provide a DDR1 read and write self-test project based on the [FPGA+DDR1 board](#Hardware Design Example) I designed. The project directory is [example-selftest](./example-selftest) , please open it with Quartus. + +This project contains following files: + +| File Name | Introduction | +| :--------------------------------------- | :----------------------------------------------------------- | +| example-selftest/fpga_top.v | top-level module | +| example-selftest/axi_self_test_master.v | AXI4 master. First, write the data into DDR1 through AXI4, and then read back and verify. | +| RTL/ddr_sdram_ctrl.v | DDR1 controller | + +The behavior of the demo project is: + +**Write**: After the project starts running, it will first write the entire DDR1 through AXI4, and directly write the address word to the corresponding address. For example, if the data width of AXI4 is 16bit, then write 0x0001 at address 0x000002. Write 0x3456 at address 0x123456. + +**Read & Error Check**: After writing the entire DDR1, the project will repeatedly read the entire DDR1 round by round. error) to generate a high-level pulse, and the error count signal (error_cnt) +1. If the DDR1 configuration is correct, there should be no error signal. You can measure the pin corresponding to error_cnt, if it is 0 (all low), it means there is no error. + +**SignalTap waveform capture**: This project contains a SignalTap file stp1.stp, which can be used to view the waveform on the DDR1 interface when the program is running. It triggers with error=1, so if there is no error in the read and write self-test, it will not be triggered. Because the project is reading DDR1 at any time, if you want to see the waveform on the DDR1 interface, just press the "Stop" button. + +**Modify AXI4 burst length**: You can modify WBURST_LEN and RBURST_LEN in lines 82 and 83 of fpga_top.v to modify the write/read burst length during self-test. The self-test program only supports 2^n-1 This burst length, ie WBURST_LEN and RBURST_LEN must take values like 0, 1, 3, 7, 15, 31, ... (note that this is only a limitation of the self-test program I wrote, DDR1 controller supports 0 Any burst length between ~255. + +> WBURST_LEN and RBURST_LEN can be set differently. + +## UART DDR1 read/write demo + +I provide a UART read and write project based on the [FPGA+DDR1 board](#Hardware Design Example) I designed. In this project, you can read and write DDR1 with different burst lengths through UART commands. The project directory is [example-uart-read-write](./example-uart-read-write) , please open it with Quartus. + +The project contains the following files: + +| File Name | Introduction | +| :----------------------------------- | :----------------------------------------------------------- | +| example-uart-read-write/fpga_top.v | top-level module | +| example-uart-read-write/uart/uart2axi4.v | an AXI4 master, which can convert the commands received by UART RX into AXI4 read and write operations, and send the data read out by read operations through UART TX. see https://github.com/WangXuan95/Verilog-UART for detail. | +| example-uart-read-write/uart/uart_rx.v | called by uart2axi4.v | +| example-uart-read-write/uart/uart_tx.v | called by uart2axi4.v | +| RTL/ddr_sdram_ctrl.v | DDR1 controller | + +There is a CH340E chip (USB to UART) on the [FPGA+DDR1 board](#Hardware Design Example), so after plugging in the USB cable, you can see the serial port corresponding to the UART on the computer (you need to download and install the driver of CH341 on [www.wch. cn/product/CH340.html](http://www.wch.cn/product/CH340.html) first). + +After the project is programed to FPGA, double-click to open a serial port tool **UartSession.exe** (it is in the [example-uart-read-write](./example-uart-read-write) directory), open the COM port corresponding to the board according to the prompt, and then type the following command + Enter, you can Write the 4 data 0x0123, 0x4567, 0x89ab, and 0xcdef into the starting address 0x12345. (A write operation with a burst length of 4 occurs on the AXI4 bus). + + w12345 0123 4567 89ab cdef + +Then use the following command + enter to read 8 data from the starting address 0x12345. + + r12345 8 + +The effect is as shown in the figure below. The first 4 data (0123 4567 89ab cdef) are what we have written into DDR1, and the last 4 data are random data that comes with DDR1 after initialization. + +| ![](./figures/UartSession.png) | +| :------------------------------------------------------: | +| Figure : Use UartSession.exe to perform DDR1 read/write. | + +The length of the write burst is how much data there is in the write command. For example, the burst length of the following write command is 10, and 10 pieces of data are written to the starting address 0x00000 + + w0 1234 2345 3456 4567 5678 6789 789a 89ab 9abc abcd + +The read command needs to specify the burst length. For example, the burst length of the following command is 30 (0x1e), and 30 data are read from the starting address 0x00000 + + r0 1e + +  + +# RTL Simulation + +The files required for the simulation are in the [SIM](./SIM) folder, where: + +| File Name | Introduction | +| :------------------------ | :----------------------------------------------------------- | +| tb_ddr_sdram_ctrl.v | Testbench code, simulation top-level. | +| axi_self_test_master.v | AXI4 master. First, write the data into DDR1 through AXI4, and then read back and verify. | +| micron_ddr_sdram_model.v | [MICRON's DDR1 simulation model](https://www.micron.com/products/dram/ddr-sdram/part-catalog/mt46v64m8p-5b) | + +The behavior of the simulation project is the same as that of the self-test program, axi_self_test_master.v, as the AXI4 host, writes regular data into DDR1, but not all of them, only the first 16KB of DDR1 (because the memory spave of the simulation model is limited), and then repeatedly read data round by round to compare whether there is unmatched data, if there is, generate a high level of one clock cycle on the `error` signal. + +Before using iverilog for simulation, you need to install iverilog , see: [iverilog_usage](https://github.com/WangXuan95/WangXuan95/blob/main/iverilog_usage/iverilog_usage.md) + +Then double-click tb_ddr_sdram_ctrl_run_iverilog.bat to run the simulation, and then you can open the generated dump.vcd file to view the waveform. + +## Modify Simulation Attributes + +The default configuration parameters of the above simulation fit MT46V64M8, namely `ROW_BITS=13`, `COL_BITS=11`, and `DQ_BITS=8`. If you want to simulate other DDR1 chips, you need to modify them in tb_ddr_sdram_ctrl.v. For MICRON's DDR1 series, these parameters should be modified as follows: + +| DDR1 part | BA_BITS | ROW_BITS | COL_BITS | DQ_LEVEL | +| :--------: | :-----: | :------: | :------: | :------: | +| MT46V64M4 | 2 | 13 | 11 | 0 | +| MT46V128M4 | 2 | 13 | 12 | 0 | +| MT46V256M4 | 2 | 14 | 12 | 0 | +| MT46V32M8 | 2 | 13 | 10 | 1 | +| MT46V64M8 | 2 | 13 | 11 | 1 | +| MT46V128M8 | 2 | 14 | 11 | 1 | +| MT46V16M16 | 2 | 13 | 9 | 2 | +| MT46V32M16 | 2 | 13 | 10 | 2 | +| MT46V64M16 | 2 | 14 | 10 | 2 | + +In addition, you can modify lines 18\~19 of tb_ddr_sdram_ctrl.v to modify the burst length of reads and writes during simulation. + +  + +# Related Links + +* MICRON's DDR1 simulation model : https://www.micron.com/products/dram/ddr-sdram/part-catalog/mt46v64m8p-5b +* MT46V64M8 datasheet : https://media-www.micron.com/-/media/client/global/documents/products/data-sheet/dram/ddr1/512mb_ddr.pdf?rev=4e1e995d6d2240e293286770f193d57d + +  + +  + +  + +  + +  + +FPGA DDR-SDRAM +=========================== + +DDR1 SDRAM 控制器,具有 AXI4 slave 接口,接收 AXI4 读写操作进行 DDR1 读写。 + 在低端FPGA设计中用 DDR-SDRAM(DDR1)替换 SDR-SDRAM。 # 简介 @@ -32,7 +484,7 @@ FPGA DDR-SDRAM 另外,由于各代 DDR-SDRAM(例如DDR3、DDR2、DDR1)的接口时序大同小异,本库也可以方便那些熟悉 Verilog 的人来学习 DDR-SDRAM 接口。 - +  # 目录 @@ -51,7 +503,7 @@ FPGA DDR-SDRAM * [仿真](#仿真) * [修改仿真参数](#修改仿真参数) - +  # 硬件设计指南 @@ -70,6 +522,7 @@ FPGA DDR-SDRAM | ddr_dqs | inout | 取决于芯片型号 | | 2.5V LVTTL | 布线尽量短 | | ddr_dm、ddr_dq | inout | 取决于芯片型号 | 与 ddr_dqs 双沿同步 | 2.5V LVTTL | 布线尽量短,与 ddr_dqs 大致等长(别太离谱即可) | +  # 硬件设计示例 @@ -83,11 +536,11 @@ FPGA DDR-SDRAM 该板子的设计在立创EDA中开放,见 [oshwhub.com/wangxuan/fpga-ddr-ce-shi-ban](https://oshwhub.com/wangxuan/fpga-ddr-ce-shi-ban)。 - +  # DDR1控制器模块 -DDR1 控制器代码见 RTL/ddr_sdram_ctrl.sv ,它能自动对 DDR1 进行初始化,并定时进行刷新(Refresh)。该模块有一个简化但完备的 AXI4 从接口,通过它可以完成对 DDR1 的读写。本节详细解释该模块的使用方法。 +DDR1 控制器代码见 RTL/ddr_sdram_ctrl.v ,它能自动对 DDR1 进行初始化,并定时进行刷新(Refresh)。该模块有一个简化但完备的 AXI4 从接口,通过它可以完成对 DDR1 的读写。本节详细解释该模块的使用方法。 ## 模块参数 @@ -330,7 +783,7 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 _______________________ ddr_a XXXXXX__RA_XXXXXXX_CA0_X_CA1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - +  # 示例程序 @@ -342,9 +795,9 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 | 文件名称 | 用途 | | :---- | :--- | -| example-selftest/top.sv | 顶层 | -| example-selftest/axi_self_test_master.sv | 是 AXI4 主机,通过 AXI4 先把有规律的数据写入 DDR1,然后读回,比较读回的数据是否符合规律,并对不匹配的情况进行计数。 | -| RTL/ddr_sdram_ctrl.sv | DDR1 控制器 | +| example-selftest/fpga_top.v | 顶层 | +| example-selftest/axi_self_test_master.v | 是 AXI4 主机,通过 AXI4 先把有规律的数据写入 DDR1,然后读回,比较读回的数据是否符合规律,并对不匹配的情况进行计数。 | +| RTL/ddr_sdram_ctrl.v | DDR1 控制器 | 该示例程序的行为是: @@ -354,7 +807,7 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 **SignalTap抓波形**:该工程包含一个 SignalTap 文件 stp1.stp,在程序运行时,可以用它查看 DDR1 接口上的波形。它以 error=1 为触发信号,因此如果读写自测没有出错,它就不会被触发。因为该工程随时都在读取 DDR1,要想看 DDR1 接口上的波形,直接按“停止”按钮即可。 -**修改 AXI4 突发长度**:在 top.sv 的第 82,83 行可以修改 WBURST_LEN 和 RBURST_LEN,从而修改自测时的写/读突发长度,该自测程序只支持 2^n-1 这种突发长度,即 WBURST_LEN 和 RBURST_LEN 必须取形如 0,1,3,7,15,31,…… 的值(注意,这只是我编写的自测程序的限制,DDR1 控制器是支持 0~255 之间的任意突发长度的。 +**修改 AXI4 突发长度**:在 fpga_top.v 的第 82,83 行可以修改 WBURST_LEN 和 RBURST_LEN,从而修改自测时的写/读突发长度,该自测程序只支持 2^n-1 这种突发长度,即 WBURST_LEN 和 RBURST_LEN 必须取形如 0,1,3,7,15,31,…… 的值(注意,这只是我编写的自测程序的限制,DDR1 控制器是支持 0~255 之间的任意突发长度的。 > WBURST_LEN 和 RBURST_LEN 可以设置的不一样。 @@ -366,9 +819,11 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 | 文件名称 | 用途 | | :---- | :--- | -| example-uart-read-write/top.sv | 顶层 | -| example-uart-read-write/uart2axi4.sv | 是 AXI4 主机,能把 UART RX 收到的命令转换成 AXI4 读写操作,并把读操作读出的数据通过 UART TX 发送出去 | -| RTL/ddr_sdram_ctrl.sv | DDR1 控制器 | +| example-uart-read-write/fpga_top.v | 顶层 | +| example-uart-read-write/uart/uart2axi4.v | 是 AXI4 主机,能把 UART RX 收到的命令转换成 AXI4 读写操作,并把读操作读出的数据通过 UART TX 发送出去 (详见 https://github.com/WangXuan95/Verilog-UART) | +| example-uart-read-write/uart/uart_rx.v | 被 uart2axi4.v 调用 | +| example-uart-read-write/uart/uart_tx.v | 被 uart2axi4.v 调用 | +| RTL/ddr_sdram_ctrl.v | DDR1 控制器 | [FPGA+DDR1测试板](#硬件设计示例)上有一个 CH340E 芯片(USB 转 UART),因此插上 USB 线后就可以在电脑上看见 UART 对应的 COM 口(需要先在 [www.wch.cn/product/CH340.html](http://www.wch.cn/product/CH340.html) 下载安装 CH341 的驱动)。 @@ -376,9 +831,9 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 w12345 0123 4567 89ab cdef -然后用以下命令+回车,可以以 0x12345 为起始地址,以 7 为突发长度,读取 8 个数据。 +然后用以下命令+回车,可以以 0x12345 为起始地址,以 8 为突发长度,读取 8 个数据。 - r12345 7 + r12345 8 效果如下图,前4个数据 (0123 4567 89ab cdef) 就是我们已经写入 DDR1 的,后4个数据我们没写过,是 DDR1 初始化后自带的随机数据。 @@ -386,15 +841,15 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 | :--: | | 图:使用 UartSession.exe 测试 DDR1 读写 | -写命令里有多少数据,写突发长度就是多少,例如以下写命令的突发长度是 9,将 10 个数据写入起始地址 0x00000 +写命令里有多少数据,写突发长度就是多少,例如以下写命令的突发长度是 10,将 10 个数据写入起始地址 0x00000 w0 1234 2345 3456 4567 5678 6789 789a 89ab 9abc abcd -读命令则直接指定突发长度,例如以下命令的突发长度为 30 (0x1e),从起始地址 0x00000 将 31 个数据读出 +读命令则直接指定突发长度,例如以下命令的突发长度为 30 (0x1e),从起始地址 0x00000 将 30 个数据读出 r0 1e - +  # 仿真 @@ -402,11 +857,11 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 | 文件路径 | 用途 | | :---- | :--- | -| tb_ddr_sdram_ctrl.sv | 仿真顶层 | -| axi_self_test_master.sv | 是 AXI4 主机,通过 AXI4 先把有规律的数据写入 DDR1,然后读回,比较读回的数据是否符合规律,并对不匹配的情况进行计数。 | -| micron_ddr_sdram_model.sv | [MICRON 公司提供的 DDR1 仿真模型](https://www.micron.com/products/dram/ddr-sdram/part-catalog/mt46v64m8p-5b) | +| tb_ddr_sdram_ctrl.v | 仿真顶层 | +| axi_self_test_master.v | 是 AXI4 主机,通过 AXI4 先把有规律的数据写入 DDR1,然后读回,比较读回的数据是否符合规律,并对不匹配的情况进行计数。 | +| micron_ddr_sdram_model.v | [MICRON 公司提供的 DDR1 仿真模型](https://www.micron.com/products/dram/ddr-sdram/part-catalog/mt46v64m8p-5b) | -该仿真工程的行为和自测程序一样, axi_self_test_master.sv 作为 AXI4 主机,将有规律的数据写入 DDR1 中,只不过不是全部写入,而是只写入 DDR1 的前 16KB (因为仿真模型的存储空间有限),然后一轮一轮地反复读出数据,比较是否有不匹配的数据,若有,则在 error 信号上产生一个时钟周期的高电平。 +该仿真工程的行为和自测程序一样, axi_self_test_master.v 作为 AXI4 主机,将有规律的数据写入 DDR1 中,只不过不是全部写入,而是只写入 DDR1 的前 16KB (因为仿真模型的存储空间有限),然后一轮一轮地反复读出数据,比较是否有不匹配的数据,若有,则在 error 信号上产生一个时钟周期的高电平。 使用 iverilog 进行仿真前,需要安装 iverilog ,见:[iverilog_usage](https://github.com/WangXuan95/WangXuan95/blob/main/iverilog_usage/iverilog_usage.md) @@ -414,7 +869,7 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 ## 修改仿真参数 -以上仿真默认配置的参数是使用 MT46V64M8 ,即 ROW_BITS=13,COL_BITS=11,DQ_BITS=8 。如果想对其它型号的 DDR1 芯片进行仿真,你需要在 tb_ddr_sdram_ctrl.sv 里修改它们。对于 MICRON 公司的 DDR1 系列,这些参数应该这样修改: +以上仿真默认配置的参数是使用 MT46V64M8 ,即 ROW_BITS=13,COL_BITS=11,DQ_BITS=8 。如果想对其它型号的 DDR1 芯片进行仿真,你需要在 tb_ddr_sdram_ctrl.v 里修改它们。对于 MICRON 公司的 DDR1 系列,这些参数应该这样修改: | 芯片名称 | BA_BITS | ROW_BITS | COL_BITS | DQ_LEVEL | | :--: | :--: | :--: | :--: | :--: | @@ -428,448 +883,12 @@ AXI4 总线的地址(awaddr和araddr)统一是字节地址,模块会根据 | MT46V32M16 | 2 | 13 | 10 | 2 | | MT46V64M16 | 2 | 14 | 10 | 2 | -另外,你可以修改 tb_ddr_sdram_ctrl.sv 的第 18 和 19 行来修改仿真时的突发读写的长度。 - +另外,你可以修改 tb_ddr_sdram_ctrl.v 的第 18 和 19 行来修改仿真时的突发读写的长度。 +  # 参考资料 * MICRON 公司提供的 DDR1 仿真模型: https://www.micron.com/products/dram/ddr-sdram/part-catalog/mt46v64m8p-5b * MT46V64M8 芯片手册: https://media-www.micron.com/-/media/client/global/documents/products/data-sheet/dram/ddr1/512mb_ddr.pdf?rev=4e1e995d6d2240e293286770f193d57d - - - - -FPGA DDR-SDRAM -=========================== - -Replacing SDR-SDRAM with DDR-SDRAM (DDR1) in low-end FPGA designs. - -# Introduction - - |------------------------------| |-----------| - | | | | - ----->| driving clock and reset | | | - | | | | - ------------| | DDR-SDRAM interface |--------->| | - | AXI4 | | | | - AXI4 master |-------->| AXI4 slave | | | - | | | | | - ------------| |------------------------------| |-----------| - User Logic DDR1 Controller (ddr_sdram_ctrl.sv) DDR1 chip - -Many low-end FPGA development boards use SDR-SDRAM as off-chip memory, but DDR-SDRAM (DDR1) is larger and less expensive than SDR-SDRAM. And like SDR-SDRAM, DDR1 can also be directly driven by common IO pins of low-end FPGAs. I write a soft core DDR1 controller with a slave AXI4 interface for user. The features of this controller are: - -* **Platform Independent** : Written in pure SystemVerilog and can run on various FPGAs including Altera and Xilinx. -* **Compatible** : Supports DDR1 of various bit widths and capacities (this has been verify by simulation on MICRON's DDR1 models). - -To demonstrate the use of this controller, I provide two demo projects: - -* **Self-test demo** : Through the DDR1 controller, write data into DDR1, then read it out, and compare whether the read data is consistent with the written data. -* **UART DDR1 read/write demo** : Convert UART commands into AXI4 bus actions to read and write DDR1 through the DDR1 controller. - -In addition, since the interface timing of each generation of DDR-SDRAM (such as DDR3, DDR2, DDR1) is similar, this repository can also facilitate those who are familiar with Verilog to learn the DDR-SDRAM interface. - - - -# Table of Contents - -* [Hardware Design Guidelines](#Hardware Design Guidelines) -* [Hard Design Example](#Hard Design Example) -* [DDR1 Control Module Manual](#DDR1 Control Module Manual) - * [Module Parameters](#Module Parameters) - * [Module Interface: clock and reset](#Module Interface: clock and reset) - * [Module Interface: DDR1 interface](#Module Interface: DDR1 interface) - * [Module Interface: AXI4 slave](#Module Interface: AXI4 slave) - * [Bit Width Parameters](#Bit Width Parameters) - * [Timing Parameters](#Timing Parameters) -* [FPGA Demo Projects](#FPGA Demo Projects) - * [Self-test demo](#Self-test demo) - * [UART DDR1 read/write demo](#UART DDR1 read/write demo) -* [RTL Simulation](#RTL Simulation) - * [Modify Simulation Attributes](#Modify Simulation Attributes) - - - -# Hardware Design Guidelines - -For FPGA selection, only an FPGA with a sufficient number of common IOs can drive DDR1. The IO level standard of DDR1 is often SSTL-2, which is compatible with 2.5V LVTTL or 2.5V LVCMOS, so the power supply of the corresponding FPGA IO bank should be 2.5V, and should be configured as 2.5V LVTTL or 2.5V LVCMOS in the FPGA development software . - -The following table shows the pin defination of the DDR1 chip and the points that should be paid attention to when connecting it to the FPGA. - -| pin name | direction | width | introduction | level | note | -| :----------------------------: | :---------: | :-----------------: | :-------------------------------- | :--------- | :------------------------------- | -| ddr_ck_p | FPGA output | 1 | DDR1 clock p , ≥75MHz | 2.5V LVTTL | differential route with ddr_ck_n | -| ddr_ck_n | FPGA output | 1 | DDR1 clock n , ≥75MHz | 2.5V LVTTL | differential route with ddr_ck_p | -| ddr_cke, ddr_cs_n | FPGA output | 1 | low speed | 2.5V LVTTL | | -| ddr_ras_n, ddr_cas_n, ddr_we_n | FPGA output | 1 | sync with ddr_ck_p's falling edge | 2.5V LVTTL | same length as ddr_ck_p | -| ddr_ba | FPGA output | 2 | sync with ddr_ck_p's falling edge | 2.5V LVTTL | same length as ddr_ck_p | -| ddr_a | FPGA output | depend on DDR1 part | sync with ddr_ck_p's falling edge | 2.5V LVTTL | same length as ddr_ck_p | -| ddr_dqs | inout | depend on DDR1 part | | 2.5V LVTTL | | -| ddr_dm、ddr_dq | inout | depend on DDR1 part | sync with ddr_dqs's double edge | 2.5V LVTTL | same length as ddr_dqs | - - -# Hard Design Example - -For demonstration, I used the cheapest FPGA of Altera Cyclone IV (model: EP4CE6E22C8N) and MICRON's 64MB DDR1 (model MT46V64M8TG-6T) to design a small board. All the demo project of this repository can run on this board directly. If you want to use DDR1 in your own PCB design, just refer to this board's design. - -| ![board-image](./figures/board.jpg) | -| :----------------------------------: | -| **Figure** : FPGA + DDR1 demo board. | - -See board schematic [PCB/sch.pdf](./PCB/sch.pdf) and manufacturing file [PCB/gerber.zip](./PCB/gerber.zip) . It is a double-layer board, and it is not necessary to pay attention to impedance matching like DDR2 and DDR3, because the operating frequency of the circuit is 75MHz on both sides, which is not particularly high. Just pay attention to keeping the distance between FPGA and DDR as close as possible, and the wiring as short as possible. For example, I put the DDR1 chip on the opposite side of the FPGA chip to keep the wiring short. - -The design of this board is open in LCEDA, see [oshwhub.com/wangxuan/fpga-ddr-ce-shi-ban](https://oshwhub.com/wangxuan/fpga-ddr-ce-shi-ban) . - - - -# DDR1 Control Module Manual - -See [RTL/ddr_sdram_ctrl.sv](./RTL/ddr_sdram_ctrl.sv) for the DDR1 controller code, which can automatically initialize DDR1 and refresh it regularly. The module has a AXI4 slave interface through which reads and writes to DDR1 can be accomplished. This section introduce in detail how to use this module. - -## Module Parameters - -Verilog parameters of this module are defined as follows: - -```Verilog -module ddr_sdram_ctrl #( - parameter READ_BUFFER = 1, - parameter BA_BITS = 2, - parameter ROW_BITS = 13, - parameter COL_BITS = 11, - parameter DQ_LEVEL = 1, - parameter [9:0] tREFC = 10'd256, - parameter [7:0] tW2I = 8'd7, - parameter [7:0] tR2I = 8'd7 -) -``` - -These parameters are described in the table below: - -| Parameter | Type | value range | default value | introduction | -| :---------: | :----: | :---------: | :-----------: | :----------------------------------------------------------- | -| READ_BUFFER | | 0 or 1 | 1 | If it is set to 0, there will be no read data buffer in the DDR1 controller, and the read data will not wait for the AXI4 host to accept it, that is, the rvalid signal will not wait for the rready signal, and will be directly poured out at the highest rate. At this time, AXI4 is not Complete, but can reduce the latency of readout. If it is set to 1, there will be a large enough read data buffer in the DDR1 controller, and the rvalid signal will shake hands with the rready signal to confirm that the AXI4 host is ready before reading the data. | -| BA_BITS | Width | 1~4 | 2 | The width of DDR BANK ADDRESS (ddr_ba) is specified. Regular DDR1 BANK ADDRESS is 2bit, so this parameter is usually fixed to the default value and does not need to be changed. | -| ROW_BITS | Width | 1~15 | 13 | It specifies the width of the DDR ROW ADDRESS, and also determines the width of the address line pins (ddr_a) of the DDR1 chip. This parameter depends on the selection of the DDR1 chip. For example, MT46V64M8 has 8192 COLs per bank, considering 2^11=8192, this parameter should be 13. Similarly, for MT46V128M8, this parameter should be 14 | -| COL_BITS | Width | 1~14 | 11 | Specifies the width of the DDR COL ADDRESS. This parameter depends on the selection of the DDR1 chip. For example, MT46V64M8 has 2048 COLs per ROW. Considering 2^11=2048, this parameter should be 11. Similarly, for MT46V32M16, this parameter should be 10. | -| DQ_LEVEL | Width | 0~7 | 1 | The data bit width of the DDR1 chip is specified. For a chip with a bit width of x4 (such as MT46V64M4), this parameter should be 0; for a chip with a bit width of x8 (such as MT46V64M8), this parameter should be 1; for a chip with a bit width of x16 ( For example, MT46V64M16), this parameter should be 2; for the case of bit width x32 (such as two pieces of MT46V64M16 extended bit width), this parameter should be 3; and so on. | -| tREFC | Timing | 1\~1023 | 256 | The controller will refresh DDR1 periodically, and this parameter specifies the refresh interval of DDR1. See [Timing Parameters](#Timing Parameters) for details. | -| tW2I | Timing | 1\~255 | 7 | This parameter specifies the interval from the last write command of a write action to the activation command (ACT) of the next action. See [Timing Parameters](#Timing Parameters) for details. | -| tR2I | Timing | 1\~255 | 7 | This parameter specifies the interval from the last read command of a read action to the activation command (ACT) of the next action. See [Timing Parameters](#Timing Parameters) for details. | - -## Module Interface: clock and reset - -This module requires a drive clock and a drive reset, as follows: - -```Verilog - input wire rstn_async, - input wire drv_clk, -``` - -`rstn_async` is a low-level reset signal and should be set high during normal operation. `drv_clk` is the driving clock, and its frequency is 4 times the user clock. - -Before the module starts working, the `rstn_async` signal should be set low to reset the module, and then set rstn_async high to release the reset. - -### Clock freqency selection - -In this module, the driving clock `drv_clk` is divided by 4 to generate the DDR1 clock (`ddr_ck_p/ddr_ck_n`) and the AXI4 bus user clock (`clk`). This section describes how to determine the frequency of the drive clock `drv_clk`. - -First, the clock frequency is limited by the DDR1 chip. Considering that all DDR1 interface frequencies are at least 75MHz, the lower limit of `drv_clk` is 75\*4=300MHz. - -The upper limit of `drv_clk` also depends on the chip model of DDR1. For example, for MT46V64M8P-5B, check the chip manual, the maximum clock frequency of DDR1 with -5B suffix is 133MHz when CAS Latency (CL)=2, then the upper limit of `drv_clk` is 133 \*4=532MHz. - -> Note: This controller has a fixed CAS Latency (CL) = 2. - -In addition, the upper limit of the clock frequency is also limited by the speed of the FPGA. Too high a clock frequency can easily lead to timing failure. This design fully considers the timing safety design, most registers work in the clk clock domain with a lower frequency; some registers work in a clock that is twice the frequency of the clk clock, and the combinational logic of the input port is very short; Under the `drv_clk` of the frequency, but the input port comes directly from the register output of the upper stage (no combinational logic). Therefore, even on the EP4CE6E22C8N with a very low speed class, the correct operation of the module is guaranteed at a drive clock of 300MHz. - -## Module Interface: DDR1 interface - -Following is the DDR1 interface of this module. These signals should be pinout directly from the FPGA and connected to the DDR1 chip. - -```Verilog - output wire ddr_ck_p, ddr_ck_n, - output wire ddr_cke, - output reg ddr_cs_n, - output reg ddr_ras_n, - output reg ddr_cas_n, - output reg ddr_we_n, - output reg [ BA_BITS-1:0] ddr_ba, - output reg [ ROW_BITS-1:0] ddr_a, - output wire [((1< Note: When the module parameter `READ_BUFFER=0`, the module will save a BRAM resource and also reduce the delay between address channel handshake and data transfer. But the DDR1 controller ignores the `rready=0` condition and does not wait for the AXI4 host to be ready to accept data. This will destroy the completeness of the AXI4 protocol, but it may be useful in some simple situations. - - -## Bit Width Parameters - -This section describes how to determine the 4 parameters: `BA_BITS` , `ROW_BITS` , `COL_BITS` and `DQ_LEVEL`. - -Take [MICRON's DDR-SDRAM](https://www.micron.com/products/dram/ddr-sdram) series chips as an example, different chips have different ROW ADDRESS BITS, COL ADDRESS BITS and DATA BITS, which means that their bit width parameters are also different, as shown in the following table (note: these parameters can be found in the chip datasheet). - -| chip part | ddr_dq width | ddr_dm ddr_dqs width | DQ_LEVEL | BA_BITS | ROW_BITS | COL_BITS | byte per row | total capacity | awaddr/araddr width | -| :--------: | :----------: | :------------------: | :------: | :-----: | :------: | :------: | :----------: | :------------------------- | :-----------------: | -| MT46V64M4 | 4 | 1 | 0 | 2 | 13 | 11 | 1024 | 4\*2^(2+13+11)=256Mb=32MB | 25 | -| MT46V128M4 | 4 | 1 | 0 | 2 | 13 | 12 | 2048 | 4\*2^(2+13+12)=512Mb=64MB | 26 | -| MT46V256M4 | 4 | 1 | 0 | 2 | 14 | 12 | 4096 | 4\*2^(2+14+12)=1Gb=128MB | 27 | -| MT46V32M8 | 8 | 1 | 1 | 2 | 13 | 10 | 1024 | 8\*2^(2+13+10)=256Mb=32MB | 25 | -| MT46V64M8 | 8 | 1 | 1 | 2 | 13 | 11 | 2048 | 8\*2^(2+13+11)=512Mb=64MB | 26 | -| MT46V128M8 | 8 | 1 | 1 | 2 | 14 | 11 | 4096 | 8\*2^(2+14+11)=1Gb=128MB | 27 | -| MT46V16M16 | 16 | 2 | 2 | 2 | 13 | 9 | 1024 | 16\*2^(2+13+9)=256Mb=32MB | 25 | -| MT46V32M16 | 16 | 2 | 2 | 2 | 13 | 10 | 2048 | 16\*2^(2+13+10)=512Mb=64MB | 26 | -| MT46V64M16 | 16 | 2 | 2 | 2 | 14 | 10 | 4096 | 16\*2^(2+14+10)=1Gb=128MB | 27 | - -## Timing Parameters - -This section describes how to determine the 3 timing parameters `tREFC`, `tW2I` and `tR2I`. - -We know that DDR1 requires periodic refresh action, and `tREFC` specifies the refresh clock cycle interval (subject to clk). For example, if the user clock is 75MHz, according to the chip manual of MT46V64M8, it needs to be refreshed once at most 7.8125us. Considering that 75MHz * 7.8125us = 585.9, this parameter can be set to a value less than 585, such as `10'd512`. - -`tW2I` specifies the minimum number of clock cycles (in clk) from the last write command of a write operation to the active command (ACT) of the next operation. The following figure shows a write operation on a DDR1 interface. The first rising edge of ddr_cas_n represents the end of the last write command of a write operation, and the second falling edge of ddr_ras_n represents the next operation (may be read, write, Refresh), there are 5 clock cycles between them, `tW2I` is used to specify the lower limit of the number of cycles. The default value of `tW2I` is `8'd7`, which is a conservative value compatible with most DDR1s. For different DDR1 chips, there are different shrinkage margins (see DDR1 chip datasheet for details). - - __ __ __ __ __ __ __ __ __ __ __ __ - ddr_ck_p __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__ - _____ _______________________________________________ ________ - ddr_ras_n \_____/ \_____/ - _________________ ____________________________________________ - ddr_cas_n \___________/ - _________________ ____________________________________________ - ddr_we_n \___________/ - _____ - ddr_a[10] XXXXXXXXXXXXXXXXXX_____/ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - _______________________ - ddr_ba XXXXXX__________BA___________XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - _______________________ - ddr_a XXXXXX__RA_XXXXXXX_CA0_X_CA1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - -`tR2I` specifies the minimum number of clock cycles (in `clk`) from the last read command of a read operation to the active command (ACT) of the next operation. The figure below shows a read operation on a DDR1 interface. The first rising edge of ddr_cas_n represents the end of the last read command of a read operation, and the second falling edge of ddr_ras_n represents the next operation (may be read, write, Refresh), there are 5 clock cycles between them, `tR2I` is used to specify the lower limit of the number of cycles. The default value of `tR2I` is `8'd7`, which is a conservative value compatible with most DDR1. For different DDR1 chips, there are different shrinkage margins (see DDR1 chip datasheet for details). - - __ __ __ __ __ __ __ __ __ __ __ __ - ddr_ck_p __/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__/ \__ - _____ _______________________________________________ ________ - ddr_ras_n \_____/ \_____/ - _________________ ____________________________________________ - ddr_cas_n \___________/ - __________________________________________________________________________ - ddr_we_n - _____ - ddr_a[10] XXXXXXXXXXXXXXXXXX_____/ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - _______________________ - ddr_ba XXXXXX__________BA___________XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - _______________________ - ddr_a XXXXXX__RA_XXXXXXX_CA0_X_CA1_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX - - - -# FPGA Demo Projects - -## Self-test demo - -I provide a DDR1 read and write self-test project based on the [FPGA+DDR1 board](#Hardware Design Example) I designed. The project directory is [example-selftest](./example-selftest) , please open it with Quartus. - -This project contains following files: - -| File Name | Introduction | -| :--------------------------------------- | :----------------------------------------------------------- | -| example-selftest/top.sv | top-level module | -| example-selftest/axi_self_test_master.sv | AXI4 master. First, write the data into DDR1 through AXI4, and then read back and verify. | -| RTL/ddr_sdram_ctrl.sv | DDR1 controller | - -The behavior of the demo project is: - -**Write**: After the project starts running, it will first write the entire DDR1 through AXI4, and directly write the address word to the corresponding address. For example, if the data width of AXI4 is 16bit, then write 0x0001 at address 0x000002. Write 0x3456 at address 0x123456. - -**Read & Error Check**: After writing the entire DDR1, the project will repeatedly read the entire DDR1 round by round. error) to generate a high-level pulse, and the error count signal (error_cnt) +1. If the DDR1 configuration is correct, there should be no error signal. You can measure the pin corresponding to error_cnt, if it is 0 (all low), it means there is no error. - -**SignalTap waveform capture**: This project contains a SignalTap file stp1.stp, which can be used to view the waveform on the DDR1 interface when the program is running. It triggers with error=1, so if there is no error in the read and write self-test, it will not be triggered. Because the project is reading DDR1 at any time, if you want to see the waveform on the DDR1 interface, just press the "Stop" button. - -**Modify AXI4 burst length**: You can modify WBURST_LEN and RBURST_LEN in lines 82 and 83 of top.sv to modify the write/read burst length during self-test. The self-test program only supports 2^n-1 This burst length, ie WBURST_LEN and RBURST_LEN must take values like 0, 1, 3, 7, 15, 31, ... (note that this is only a limitation of the self-test program I wrote, DDR1 controller supports 0 Any burst length between ~255. - -> WBURST_LEN and RBURST_LEN can be set differently. - -## UART DDR1 read/write demo - -I provide a UART read and write project based on the [FPGA+DDR1 board](#Hardware Design Example) I designed. In this project, you can read and write DDR1 with different burst lengths through UART commands. The project directory is [example-uart-read-write](./example-uart-read-write) , please open it with Quartus. - -The project contains the following files: - -| File Name | Introduction | -| :----------------------------------- | :----------------------------------------------------------- | -| example-uart-read-write/top.sv | top-level module | -| example-uart-read-write/uart2axi4.sv | an AXI4 master, which can convert the commands received by UART RX into AXI4 read and write operations, and send the data read out by read operations through UART TX | -| RTL/ddr_sdram_ctrl.sv | DDR1 controller | - -There is a CH340E chip (USB to UART) on the [FPGA+DDR1 board](#Hardware Design Example), so after plugging in the USB cable, you can see the serial port corresponding to the UART on the computer (you need to download and install the driver of CH341 on [www.wch. cn/product/CH340.html](http://www.wch.cn/product/CH340.html) first). - -After the project is programed to FPGA, double-click to open a serial port tool **UartSession.exe** (it is in the [example-uart-read-write](./example-uart-read-write) directory), open the COM port corresponding to the board according to the prompt, and then type the following command + Enter, you can Write the 4 data 0x0123, 0x4567, 0x89ab, and 0xcdef into the starting address 0x12345. (A write operation with a burst length of 4 occurs on the AXI4 bus). - - w12345 0123 4567 89ab cdef - -Then use the following command + enter to read 8 data from the starting address 0x12345 (7 means the burst length is 8). - - r12345 7 - -The effect is as shown in the figure below. The first 4 data (0123 4567 89ab cdef) are what we have written into DDR1, and the last 4 data are random data that comes with DDR1 after initialization. - -| ![](./figures/UartSession.png) | -| :------------------------------------------------------: | -| Figure : Use UartSession.exe to perform DDR1 read/write. | - -The length of the write burst is how much data there is in the write command. For example, the burst length of the following write command is 9, and 10 pieces of data are written to the starting address 0x00000 - - w0 1234 2345 3456 4567 5678 6789 789a 89ab 9abc abcd - -The read command needs to specify the burst length. For example, the burst length of the following command is 30 (0x1e), and 31 data are read from the starting address 0x00000 - - r0 1e - - - -# RTL Simulation - -The files required for the simulation are in the [SIM](./SIM) folder, where: - -| File Name | Introduction | -| :------------------------ | :----------------------------------------------------------- | -| tb_ddr_sdram_ctrl.sv | Testbench code, simulation top-level. | -| axi_self_test_master.sv | AXI4 master. First, write the data into DDR1 through AXI4, and then read back and verify. | -| micron_ddr_sdram_model.sv | [MICRON's DDR1 simulation model](https://www.micron.com/products/dram/ddr-sdram/part-catalog/mt46v64m8p-5b) | - -The behavior of the simulation project is the same as that of the self-test program, axi_self_test_master.sv, as the AXI4 host, writes regular data into DDR1, but not all of them, only the first 16KB of DDR1 (because the memory spave of the simulation model is limited), and then repeatedly read data round by round to compare whether there is unmatched data, if there is, generate a high level of one clock cycle on the `error` signal. - -Before using iverilog for simulation, you need to install iverilog , see: [iverilog_usage](https://github.com/WangXuan95/WangXuan95/blob/main/iverilog_usage/iverilog_usage.md) - -Then double-click tb_ddr_sdram_ctrl_run_iverilog.bat to run the simulation, and then you can open the generated dump.vcd file to view the waveform. - -## Modify Simulation Attributes - -The default configuration parameters of the above simulation fit MT46V64M8, namely `ROW_BITS=13`, `COL_BITS=11`, and `DQ_BITS=8`. If you want to simulate other DDR1 chips, you need to modify them in tb_ddr_sdram_ctrl.sv. For MICRON's DDR1 series, these parameters should be modified as follows: - -| DDR1 part | BA_BITS | ROW_BITS | COL_BITS | DQ_LEVEL | -| :--------: | :-----: | :------: | :------: | :------: | -| MT46V64M4 | 2 | 13 | 11 | 0 | -| MT46V128M4 | 2 | 13 | 12 | 0 | -| MT46V256M4 | 2 | 14 | 12 | 0 | -| MT46V32M8 | 2 | 13 | 10 | 1 | -| MT46V64M8 | 2 | 13 | 11 | 1 | -| MT46V128M8 | 2 | 14 | 11 | 1 | -| MT46V16M16 | 2 | 13 | 9 | 2 | -| MT46V32M16 | 2 | 13 | 10 | 2 | -| MT46V64M16 | 2 | 14 | 10 | 2 | - -In addition, you can modify lines 18\~19 of tb_ddr_sdram_ctrl.sv to modify the burst length of reads and writes during simulation. - - - -# Related Links - -* MICRON's DDR1 simulation model : https://www.micron.com/products/dram/ddr-sdram/part-catalog/mt46v64m8p-5b -* MT46V64M8 datasheet : https://media-www.micron.com/-/media/client/global/documents/products/data-sheet/dram/ddr1/512mb_ddr.pdf?rev=4e1e995d6d2240e293286770f193d57d diff --git a/RTL/ddr_sdram_ctrl.sv b/RTL/ddr_sdram_ctrl.v similarity index 81% rename from RTL/ddr_sdram_ctrl.sv rename to RTL/ddr_sdram_ctrl.v index 1d0e82d..113c3c3 100644 --- a/RTL/ddr_sdram_ctrl.sv +++ b/RTL/ddr_sdram_ctrl.v @@ -2,8 +2,8 @@ //-------------------------------------------------------------------------------------------------------- // Module : ddr_sdram_ctrl // Type : synthesizable, IP's top -// Standard: SystemVerilog 2005 (IEEE1800-2005) -// Function: DDR-SDRAM (DDR1) controller +// Standard: Verilog 2001 (IEEE1364-2001) +// Function: DDR1 SDRAM controller // with AXI4 interface //-------------------------------------------------------------------------------------------------------- @@ -59,18 +59,32 @@ module ddr_sdram_ctrl #( inout [ (4< - + 'error' == high @@ -293,7 +293,7 @@ - 0110111111001011001100000000000000000011011111100101110110000000000000000001101111110010110011000000000000000000110111111001011101100000000000000000011011111100101100110000000000000000001101111110010111011000000000000000000110111111001011001100000000000000000011011111100101110110000000000000000001101111110010110011000000000000000000110111111001011101100000000000000000011011111100101100110000000000000000001101111110010111011000000000000000000110111111001011001100000000000000000011011111100101110110000000000000000001101111110010110011000000000000000000110111111001011101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001000000000000000000000010000000000000110000000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000000000100000000000000000010000000000000010010000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000000001000000000000000000100000000000000100100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000001010110110001011000100000000000000000101011011000101110010000000000000000010101101100010110011000000000000000001010110110001011101100000000000000000001000000010101000110000000000000000000100000001010101011000000000000000000010100000101010001100000000000000000001010000010101010110000000000000000000100100001010100011000000000000000000010010000101010101100000000000000000001011000010101000110000000000000000000101100001010101011000000000000000000010001000101010001100000000000000000001000100010101010110000000000000000000101010001010100011000000000000000000010101000101010101100000000000000000001001100010101000110000000000000000000100110001010101011000000000000000000010111000101010001100000000000000000001011100010101010110000000000000000000100001001010100011000000000000000000010000100101010101100000000000000000001010010010101000110000000000000000000101001001010101011000000000000000000010010100101010001100000000000000000001001010010101010110000000000000000000101101001010100011000000000000000000010110100101010101100000000000000000001000110010101000110000000000000000000100011001010101011000000000000000000010101100101010001100000000000000000001010110010101010110000000000000000000100111001010100011000000000000000000010011100101010101100000000000000000011011110010101000110000000000000000001101111001010101011000000000000000000110111100101011001100000000000000000011011110010101110110000000000000000001101111001010110011000000000000000000110111100101011101100000000000000000011011110010101100110000000000000000001101111001010111011000000000000000000110111100101011001100000000000000000011011110010101110110000000000000000001101111001010110011000000000000000000110111100101011101100000000000000000011011110010101100110000000000000000001101111001010111011000000000000000000110111100101011001100000000000000000011011110010101110110000000000000000001101111001010110011000000000000000000110111100101011101100000000000000000101011011000101100010000000000000000010101101100010111001000000000000000001010110110001011001100000000000000000101011011000101110110000000000000000000100000101010100011000000000000000000010000010101010101100000000000000000001010001010101000110000000000000000000101000101010101011000000000000000000010010010101010001100000000000000000001001001010101010110000000000000000000101100101010100011000000000000000000010110010101010101100000000000000000001000101010101000110000000000000000000100010101010101011000000000000000000010101010101010001100000000000000000001010101010101010110000000000000000000100110101010100011000000000000000000010011010101010101100000000000000000001011101010101000110000000000000000000101110101010101011000000000000000000010000110101010001100000000000000000001000011010101010110000000000000000000101001101010100011000000000000000000010100110101010101100000000000000000001001011010101000110000000000000000000100101101010101011000000000000000000010110110101010001100000000000000000001011011010101010110000000000000000000100011101010100011000000000000000000010001110101010101100000000000000000001010111010101000110000000000000000000101011101010101011000000000000000000010011110101010001100000000000000000001001111010101010110000000000000000001101111101010100011000000000000000000110111110101010101100000000000000000011011111010101100110000000000000000001101111101010111011000000000000000000110111110101011001100000000000000000011011111010101110110000000000000000001101111101010110011000000000000000000110111110101011101100000000000000000011011111010101100110000000000000000001101111101010111011000000000000000000110111110101011001100000000000000000011011111010101110110000000000000000001101111101010110011000000000000000000110111110101011101100000000000000000011011111010101100110000000000000000001101111101010111011000000000000000000110111110101011001100000000000000000011011111010101110110000000000000000010101101100010110001000000000000000001010110110001011100100000000000000000101011011000101100110000000000000000010101101100010111011000000000000000000010000001101010001100000000000000000001000000110101010110000000000000000000101000011010100011000000000000000000010100001101010101100000000000000000001001000110101000110000000000000000000100100011010101011000000000000000000010110001101010001100000000000000000001011000110101010110000000000000000000100010011010100011000000000000000000010001001101010101100000000000000000001010100110101000110000000000000000000101010011010101011000000000000000000010011001101010001100000000000000000001001100110101010110000000000000000000101110011010100011000000000000000000010111001101010101100000000000000000001000010110101000110000000000000000000100001011010101011000000000000000000010100101101010001100000000000000000001010010110101010110000000000000000000100101011010100011000000000000000000010010101101010101100000000000000000001011010110101000110000000000000000000101101011010101011000000000000000000010001101101010001100000000000000000001000110110101010110000000000000000000101011011010100011000000000000000000010101101101010101100000000000000000001001110110101000110000000000000000000100111011010101011000000000000000000110111101101010001100000000000000000011011110110101010110000000000000000001101111011010110011000000000000000000110111101101011101100000000000000000011011110110101100110000000000000000001101111011010111011000000000000000000110111101101011001100000000000000000011011110110101110110000000000000000001101111011010110011000000000000000000110111101101011101100000000000000000011011110110101100110000000000000000001101111011010111011000000000000000000110111101101011001100000000000000000011011110110101110110000000000000000001101111011010110011000000000000000000110111101101011101100000000000000000011011110110101100110000000000000000001101111011010111011000000000000000001010110110001011000100000000000000000101011011000101110010000000000000000010101101100010110011000000000000000001010110110001011101100000000000000000001000001110101000110000000000000000000100000111010101011000000000000000000010100011101010001100000000000000000001010001110101010110000000000000000000100100111010100011000000000000000000010010011101010101100000000000000000001011001110101000110000000000000000000101100111010101011000000000000000000010001011101010001100000000000000000001000101110101010110000000000000000000101010111010100011000000000000000000010101011101010101100000000000000000001001101110101000110000000000000000000100110111010101011000000000000000000010111011101010001100000000000000000001011101110101010110000000000000000000100001111010100011000000000000000000010000111101010101100000000000000000001010011110101000110000000000000000000101001111010101011000000000000000000010010111101010001100000000000000000001001011110101010110000000000000000000101101111010100011000000000000000000010110111101010101100000000000000000001000111110101000110000000000000000000100011111010101011000000000000000000010101111101010001100000000000000000001010111110101010110000000000000000000100111111010100011000000000000000000010011111101010101100000000000000000011011111110101000110000000000000000001101111111010101011000000000000000000110111111101011001100000000000000000011011111110101110110000000000000000001101111111010110011000000000000000000110111111101011101100000000000000000011011111110101100110000000000000000001101111111010111011000000000000000000110111111101011001100000000000000000011011111110101110110000000000000000001101111111010110011000000000000000000110111111101011101100000000000000000011011111110101100110000000000000000001101111111010111011000000000000000000110111111101011001100000000000000000011011111110101110110000000000000000001101111111010110011000000000000000000110111111101011101100000000000000000101011011000101100010000000000000000010101101100010111001000000000000000001010110110001011001100000000000000000101011011000101110110000000000000000000100000000110100011000000000000000000010000000011010101100000000000000000001010000001101000110000000000000000000101000000110101011000000000000000000010010000011010001100000000000000000001001000001101010110000000000000000000101100000110100011000000000000000000010110000011010101100000000000000000001000100001101000110000000000000000000100010000110101011000000000000000000010101000011010001100000000000000000001010100001101010110000000000000000000100110000110100011000000000000000000010011000011010101100000000000000000001011100001101000110000000000000000000101110000110101011000000000000000000010000100011010001100000000000000000001000010001101010110000000000000000000101001000110100011000000000000000000010100100011010101100000000000000000001001010001101000110000000000000000000100101000110101011000000000000000000010110100011010001100000000000000000001011010001101010110000000000000000000100011000110100011000000000000000000010001100011010101100000000000000000001010110001101000110000000000000000000101011000110101011000000000000000000010011100011010001100000000000000000001001110001101010110000000000000000001101111000110100011000000000000000000110111100011010101100000000000000000011011110001101100110000000000000000001101111000110111011000000000000000000110111100011011001100000000000000000011011110001101110110000000000000000001101111000110110011000000000000000000110111100011011101100000000000000000011011110001101100110000000000000000001101111000110111011000000000000000000110111100011011001100000000000000000011011110001101110110000000000000000001101111000110110011000000000000000000110111100011011101100000000000000000011011110001101100110000000000000000001101111000110111011000000000000000000110111100011011001100000000000000000011011110001101110110000000000000000010101101100010110001000000000000000001010110110001011100100000000000000000101011011000101100110000000000000000010101101100010111011000000000000000000010000010011010001100000000000000000001000001001101010110000000000000000000101000100110100011000000000000000000010100010011010101100000000000000000001001001001101000110000000000000000000100100100110101011000000000000000000010110010011010001100000000000000000001011001001101010110000000000000000000100010100110100011000000000000000000010001010011010101100000000000000000001010101001101000110000000000000000000101010100110101011000000000000000000010011010011010001100000000000000000001001101001101010110000000000000000000101110100110100011000000000000000000010111010011010101100000000000000000001000011001101000110000000000000000000100001100110101011000000000000000000010100110011010001100000000000000000001010011001101010110000000000000000000100101100110100011000000000000000000010010110011010101100000000000000000001011011001101000110000000000000000000101101100110101011000000000000000000010001110011010001100000000000000000001000111001101010110000000000000000000101011100110100011000000000000000000010101110011010101100000000000000000001001111001101000110000000000000000000100111100110101011000000000000000000110111110011010001100000000000000000011011111001101010110000000000000000001101111100110110011000000000000000000110111110011011101100000000000000000011011111001101100110000000000000000001101111100110111011000000000000000000110111110011011001100000000000000000011011111001101110110000000000000000001101111100110110011000000000000000000110111110011011101100000000000000000011011111001101100110000000000000000001101111100110111011000000000000000000110111110011011001100000000000000000011011111001101110110000000000000000001101111100110110011000000000000000000110111110011011101100000000000000000011011111001101100110000000000000000001101111100110111011000000000000000001010110110001011000100000000000000000101011011000101110010000000000000000010101101100010110011000000000000000001010110110001011101100000000000000000001000000101101000110000000000000000000100000010110101011000000000000000000010100001011010001100000000000000000001010000101101010110000000000000000000100100010110100011000000000000000000010010001011010101100000000000000000001011000101101000110000000000000000000101100010110101011000000000000000000010001001011010001100000000000000000001000100101101010110000000000000000000101010010110100011000000000000000000010101001011010101100000000000000000001001100101101000110000000000000000000100110010110101011000000000000000000010111001011010001100000000000000000001011100101101010110000000000000000000100001010110100011000000000000000000010000101011010101100000000000000000001010010101101000110000000000000000000101001010110101011000000000000000000010010101011010001100000000000000000001001010101101010110000000000000000000101101010110100011000000000000000000010110101011010101100000000000000000001000110101101000110000000000000000000100011010110101011000000000000000000010101101011010001100000000000000000001010110101101010110000000000000000000100111010110100011000000000000000000010011101011010101100000000000000000011011110101101000110000000000000000001101111010110101011000000000000000000110111101011011001100000000000000000011011110101101110110000000000000000001101111010110110011000000000000000000110111101011011101100000000000000000011011110101101100110000000000000000001101111010110111011000000000000000000110111101011011001100000000000000000011011110101101110110000000000000000001101111010110110011000000000000000000110111101011011101100000000000000000011011110101101100110000000000000000001101111010110111011000000000000000000110111101011011001100000000000000000011011110101101110110000000000000000001101111010110110011000000000000000000110111101011011101100000000000000000101011011000101100010000000000000000010101101100010111001000000000000000001010110110001011001100000000000000000101011011000101110110000000000000000000100000110110100011000000000000000000010000011011010101100000000000000000001010001101101000110000000000000000000101000110110101011000000000000000000010010011011010001100000000000000000001001001101101010110000000000000000000101100110110100011000000000000000000010110011011010101100000000000000000001000101101101000110000000000000000000100010110110101011000000000000000000010101011011010001100000000000000000001010101101101010110000000000000000000100110110110100011000000000000000000010011011011010101100000000000000000001011101101101000110000000000000000000101110110110101011000000000000000000010000111011010001100000000000000000001000011101101010110000000000000000000101001110110100011000000000000000000010100111011010101100000000000000000001001011101101000110000000000000000000100101110110101011000000000000000000010110111011010001100000000000000000001011011101101010110000000000000000000100011110110100011000000000000000000010001111011010101100000000000000000001010111101101000110000000000000000000101011110110101011000000000000000000010011111011010001100000000000000000001001111101101010110000000000000000001101111110110100011000000000000000000110111111011010101100000000000000000011011111101101100110000000000000000001101111110110111011000000000000000000110111111011011001100000000000000000011011111101101110110000000000000000001101111110110110011000000000000000000110111111011011101100000000000000000011011111101101100110000000000000000001101111110110111011000000000000000000110111111011011001100000000000000000011011111101101110110000000000000000001101111110110110011000000000000000000110111111011011101100000000000000000011011111101101100110000000000000000001101111110110111011000000000000000000110111111011011001100000000000000000011011111101101110110000000000000000010101101100010110001000000000000000001010110110001011100100000000000000000101011011000101100110000000000000000010101101100010111011000000000000000000010000000111010001100000000000000000001000000011101010110000000000000000000101000001110100011000000000000000000010100000111010101100000000000000000001001000011101000110000000000000000000100100001110101011000000000000000000010110000111010001100000000000000000001011000011101010110000000000000000000100010001110100011000000000000000000010001000111010101100000000000000000001010100011101000110000000000000000000101010001110101011000000000000000000010011000111010001100000000000000000001001100011101010110000000000000000000101110001110100011000000000000000000010111000111010101100000000000000000001000010011101000110000000000000000000100001001110101011000000000000000000010100100111010001100000000000000000001010010011101010110000000000000000000100101001110100011000000000000000000010010100111010101100000000000000000001011010011101000110000000000000000000101101001110101011000000000000000000010001100111010001100000000000000000001000110011101010110000000000000000000101011001110100011000000000000000000010101100111010101100000000000000000001001110011101000110000000000000000000100111001110101011000000000000000000110111100111010001100000000000000000011011110011101010110000000000000000001101111001110110011000000000000000000110111100111011101100000000000000000011011110011101100110000000000000000001101111001110111011000000000000000000110111100111011001100000000000000000 + 0100111110110001101100000000000000000010011111011000100110000000000000000001001111101100011011000000000000000000100111110110001001100000000000000000010011111011000110110000000000000000001001111101100010011000000000000000000100111110110001101100000000000000000010011111011000100110000000000000000001001111101100011011000000000000000000100111110110001001100000000000000000010011111011000110110000000000000000001001111101100010011000000000000000000100111110110001101100000000000000000000010010101000100010000000000000000000001001010100011001000000000000000000000100101010001001100000000000000000000010010101000110110000000000000000000000000011100000011000000000000000000000000001110000101100000000000000000000010000111000000110000000000000000000001000011100001011000000000000000000000010001110000001100000000000000000000001000111000010110000000000000000000001100011100000011000000000000000000000110001110000101100000000000000000000000100111000000110000000000000000000000010011100001011000000000000000000000101001110000001100000000000000000000010100111000010110000000000000000000000110011100000011000000000000000000000011001110000101100000000000000000000011100111000000110000000000000000000001110011100001011000000000000000000000000101110000001100000000000000000000000010111000010110000000000000000000001001011100000011000000000000000000000100101110000101100000000000000000000001010111000000110000000000000000000000101011100001011000000000000000000000110101110000001100000000000000000000011010111000010110000000000000000000000011011100000011000000000000000000000001101110000101100000000000000000000010110111000000110000000000000000000001011011100001011000000000000000000000011101110000001100000000000000000000001110111000010110000000000000000001001111011100000011000000000000000000100111101110000101100000000000000000010011110111000100110000000000000000001001111011100011011000000000000000000100111101110001001100000000000000000010011110111000110110000000000000000001001111011100010011000000000000000000100111101110001101100000000000000000010011110111000100110000000000000000001001111011100011011000000000000000000100111101110001001100000000000000000010011110111000110110000000000000000001001111011100010011000000000000000000100111101110001101100000000000000000010011110111000100110000000000000000001001111011100011011000000000000000000100111101110001001100000000000000000010011110111000110110000000000000000000001001010100010001000000000000000000000100101010001100100000000000000000000010010101000100110000000000000000000001001010100011011000000000000000000000000011110000001100000000000000000000000001111000010110000000000000000000001000111100000011000000000000000000000100011110000101100000000000000000000001001111000000110000000000000000000000100111100001011000000000000000000000110011110000001100000000000000000000011001111000010110000000000000000000000010111100000011000000000000000000000001011110000101100000000000000000000010101111000000110000000000000000000001010111100001011000000000000000000000011011110000001100000000000000000000001101111000010110000000000000000000001110111100000011000000000000000000000111011110000101100000000000000000000000011111000000110000000000000000000000001111100001011000000000000000000000100111110000001100000000000000000000010011111000010110000000000000000000000101111100000011000000000000000000000010111110000101100000000000000000000011011111000000110000000000000000000001101111100001011000000000000000000000001111110000001100000000000000000000000111111000010110000000000000000000001011111100000011000000000000000000000101111110000101100000000000000000000001111111000000110000000000000000000000111111100001011000000000000000000100111111110000001100000000000000000010011111111000010110000000000000000001001111111100010011000000000000000000100111111110001101100000000000000000010011111111000100110000000000000000001001111111100011011000000000000000000100111111110001001100000000000000000010011111111000110110000000000000000001001111111100010011000000000000000000100111111110001101100000000000000000010011111111000100110000000000000000001001111111100011011000000000000000000100111111110001001100000000000000000010011111111000110110000000000000000001001111111100010011000000000000000000100111111110001101100000000000000000010011111111000100110000000000000000001001111111100011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000010000000000000000000000100000000000001100000000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000000001000000000000000000100000000000000100100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000000010000000000000000001000000000000001001000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000001000000000000010011000000000000000000100000000000001101100000000000000000010000000000000100110000000000000000001000000000000011011000000000000000000100000000000001001100000000000000000010000000000000110110000000000000000000001001010100010001000000000000000000000100101010001100100000000000000000000010010101000100110000000000000000000001001010100011011000000000000000000000000000001000001100000000000000000000000000000100010110000000000000000000001000000010000011000000000000000000000100000001000101100000000000000000000001000000100000110000000000000000000000100000010001011000000000000000000000110000001000001100000000000000000000011000000100010110000000000000000000000010000010000011000000000000000000000001000001000101100000000000000000000010100000100000110000000000000000000001010000010001011000000000000000000000011000001000001100000000000000000000001100000100010110000000000000000000001110000010000011000000000000000000000111000001000101100000000000000000000000010000100000110000000000000000000000001000010001011000000000000000000000100100001000001100000000000000000000010010000100010110000000000000000000000101000010000011000000000000000000000010100001000101100000000000000000000011010000100000110000000000000000000001101000010001011000000000000000000000001100001000001100000000000000000000000110000100010110000000000000000000001011000010000011000000000000000000000101100001000101100000000000000000000001110000100000110000000000000000000000111000010001011000000000000000000100111100001000001100000000000000000010011110000100010110000000000000000001001111000010010011000000000000000000100111100001001101100000000000000000010011110000100100110000000000000000001001111000010011011000000000000000000100111100001001001100000000000000000010011110000100110110000000000000000001001111000010010011000000000000000000100111100001001101100000000000000000010011110000100100110000000000000000001001111000010011011000000000000000000100111100001001001100000000000000000010011110000100110110000000000000000001001111000010010011000000000000000000100111100001001101100000000000000000010011110000100100110000000000000000001001111000010011011000000000000000000000100101010001000100000000000000000000010010101000110010000000000000000000001001010100010011000000000000000000000100101010001101100000000000000000000000001000100000110000000000000000000000000100010001011000000000000000000000100010001000001100000000000000000000010001000100010110000000000000000000000100100010000011000000000000000000000010010001000101100000000000000000000011001000100000110000000000000000000001100100010001011000000000000000000000001010001000001100000000000000000000000101000100010110000000000000000000001010100010000011000000000000000000000101010001000101100000000000000000000001101000100000110000000000000000000000110100010001011000000000000000000000111010001000001100000000000000000000011101000100010110000000000000000000000001100010000011000000000000000000000000110001000101100000000000000000000010011000100000110000000000000000000001001100010001011000000000000000000000010110001000001100000000000000000000001011000100010110000000000000000000001101100010000011000000000000000000000110110001000101100000000000000000000000111000100000110000000000000000000000011100010001011000000000000000000000101110001000001100000000000000000000010111000100010110000000000000000000000111100010000011000000000000000000000011110001000101100000000000000000010011111000100000110000000000000000001001111100010001011000000000000000000100111110001001001100000000000000000010011111000100110110000000000000000001001111100010010011000000000000000000100111110001001101100000000000000000010011111000100100110000000000000000001001111100010011011000000000000000000100111110001001001100000000000000000010011111000100110110000000000000000001001111100010010011000000000000000000100111110001001101100000000000000000010011111000100100110000000000000000001001111100010011011000000000000000000100111110001001001100000000000000000010011111000100110110000000000000000001001111100010010011000000000000000000100111110001001101100000000000000000000010010101000100010000000000000000000001001010100011001000000000000000000000100101010001001100000000000000000000010010101000110110000000000000000000000000010010000011000000000000000000000000001001000101100000000000000000000010000100100000110000000000000000000001000010010001011000000000000000000000010001001000001100000000000000000000001000100100010110000000000000000000001100010010000011000000000000000000000110001001000101100000000000000000000000100100100000110000000000000000000000010010010001011000000000000000000000101001001000001100000000000000000000010100100100010110000000000000000000000110010010000011000000000000000000000011001001000101100000000000000000000011100100100000110000000000000000000001110010010001011000000000000000000000000101001000001100000000000000000000000010100100010110000000000000000000001001010010000011000000000000000000000100101001000101100000000000000000000001010100100000110000000000000000000000101010010001011000000000000000000000110101001000001100000000000000000000011010100100010110000000000000000000000011010010000011000000000000000000000001101001000101100000000000000000000010110100100000110000000000000000000001011010010001011000000000000000000000011101001000001100000000000000000000001110100100010110000000000000000001001111010010000011000000000000000000100111101001000101100000000000000000010011110100100100110000000000000000001001111010010011011000000000000000000100111101001001001100000000000000000010011110100100110110000000000000000001001111010010010011000000000000000000100111101001001101100000000000000000010011110100100100110000000000000000001001111010010011011000000000000000000100111101001001001100000000000000000010011110100100110110000000000000000001001111010010010011000000000000000000100111101001001101100000000000000000010011110100100100110000000000000000001001111010010011011000000000000000000100111101001001001100000000000000000010011110100100110110000000000000000000001001010100010001000000000000000000000100101010001100100000000000000000000010010101000100110000000000000000000001001010100011011000000000000000000000000011001000001100000000000000000000000001100100010110000000000000000000001000110010000011000000000000000000000100011001000101100000000000000000000001001100100000110000000000000000000000100110010001011000000000000000000000110011001000001100000000000000000000011001100100010110000000000000000000000010110010000011000000000000000000000001011001000101100000000000000000000010101100100000110000000000000000000001010110010001011000000000000000000000011011001000001100000000000000000000001101100100010110000000000000000000001110110010000011000000000000000000000111011001000101100000000000000000000000011100100000110000000000000000000000001110010001011000000000000000000000100111001000001100000000000000000000010011100100010110000000000000000000000101110010000011000000000000000000000010111001000101100000000000000000000011011100100000110000000000000000000001101110010001011000000000000000000000001111001000001100000000000000000000000111100100010110000000000000000000001011110010000011000000000000000000000101111001000101100000000000000000000001111100100000110000000000000000000000111110010001011000000000000000000100111111001000001100000000000000000010011111100100010110000000000000000001001111110010010011000000000000000000100111111001001101100000000000000000010011111100100100110000000000000000001001111110010011011000000000000000000100111111001001001100000000000000000010011111100100110110000000000000000001001111110010010011000000000000000000100111111001001101100000000000000000010011111100100100110000000000000000001001111110010011011000000000000000000100111111001001001100000000000000000010011111100100110110000000000000000001001111110010010011000000000000000000100111111001001101100000000000000000010011111100100100110000000000000000001001111110010011011000000000000000000000100101010001000100000000000000000000010010101000110010000000000000000000001001010100010011000000000000000000000100101010001101100000000000000000000000000010100000110000000000000000000000000001010001011000000000000000000000100000101000001100000000000000000000010000010100010110000000000000000000000100001010000011000000000000000000000010000101000101100000000000000000000011000010100000110000000000000000000001100001010001011000000000000000000000001000101000001100000000000000000000000100010100010110000000000000000000001010001010000011000000000000000000000101000101000101100000000000000000000001100010100000110000000000000000000000110001010001011000000000000000000000111000101000001100000000000000000000011100010100010110000000000000000000000001001010000011000000000000000000000000100101000101100000000000000000000010010010100000110000000000000000000001001001010001011000000000000000000000010100101000001100000000000000000000001010010100010110000000000000000000001101001010000011000000000000000000000110100101000101100000000000000000000000110010100000110000000000000000000000011001010001011000000000000000000000101100101000001100000000000000000000010110010100010110000000000000000000000111001010000011000000000000000000000011100101000101100000000000000000010011110010100000110000000000000000001001111001010001011000000000000000000100111100101001001100000000000000000010011110010100110110000000000000000001001111001010010011000000000000000000100111100101001101100000000000000000010011110010100100110000000000000000001001111001010011011000000000000000000100111100101001001100000000000000000010011110010100110110000000000000000001001111001010010011000000000000000000100111100101001101100000000000000000010011110010100100110000000000000000001001111001010011011000000000000000000100111100101001001100000000000000000010011110010100110110000000000000000001001111001010010011000000000000000000100111100101001101100000000000000000000010010101000100010000000000000000000001001010100011001000000000000000000000100101010001001100000000000000000000010010101000110110000000000000000000000000101010000011000000000000000000000000010101000101100000000000000000000010001010100000110000000000000000000001000101010001011000000000000000000000010010101000001100000000000000000000001001010100010110000000000000000000001100101010000011000000000000000000000110010101000101100000000000000000000000101010100000110000000000000000000000010101010001011000000000000000000000101010101000001100000000000000000000010101010100010110000000000000000000000110101010000011000000000000000000000011010101000101100000000000000000000011101010100000110000000000000000000001110101010001011000000000000000000000000110101000001100000000000000000000000011010100010110000000000000000000001001101010000011000000000000000000000100110101000101100000000000000000000001011010100000110000000000000000000000101101010001011000000000000000000000110110101000001100000000000000000000011011010100010110000000000000000000000011101010000011000000000000000000000001110101000101100000000000000000000010111010100000110000000000000000000001011101010001011000000000000000000000011110101000001100000000000000000000001111010100010110000000000000000001001111101010000011000000000000000000100111110101000101100000000000000000010011111010100100110000000000000000001001111101010011011000000000000000000100111110101001001100000000000000000010011111010100110110000000000000000001001111101010010011000000000000000000100111110101001101100000000000000000010011111010100100110000000000000000001001111101010011011000000000000000000100111110101001001100000000000000000010011111010100110110000000000000000001001111101010010011000000000000000000100111110101001101100000000000000000010011111010100100110000000000000000001001111101010011011000000000000000000100111110101001001100000000000000000010011111010100110110000000000000000000001001010100010001000000000000000000000100101010001100100000000000000000000010010101000100110000000000000000000001001010100011011000000000000000000000000001101000001100000000000000000000000000110100010110000000000000000000001000011010000011000000000000000000000100001101000101100000000000000000000001000110100000110000000000000000000000100011010001011000000000000000000000110001101000001100000000000000000000011000110100010110000000000000000000000010011010000011000000000000000000000001001101000101100000000000000000000010100110100000110000000000000000000001010011010001011000000000000000000000011001101000001100000000000000000000001100110100010110000000000000000000001110011010000011000000000000000000000111001101000101100000000000000000000000010110100000110000000000000000000000001011010001011000000000000000000000100101101000001100000000000000000000010010110100010110000000000000000000000101011010000011000000000000000000000010101101000101100000000000000000000011010110100000110000000000000000000001101011010001011000000000000000000000001101101000001100000000000000000000000110110100010110000000000000000000001011011010000011000000000000000000000101101101000101100000000000000000000001110110100000110000000000000000000000111011010001011000000000000000000100111101101000001100000000000000000010011110110100010110000000000000000001001111011010010011000000000000000000100111101101001101100000000000000000010011110110100100110000000000000000001001111011010011011000000000000000000100111101101001001100000000000000000010011110110100110110000000000000000001001111011010010011000000000000000000100111101101001101100000000000000000 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 @@ -322,6 +322,6 @@ - + diff --git a/example-selftest/axi_self_test_master.sv b/example-selftest/axi_self_test_master.v similarity index 79% rename from example-selftest/axi_self_test_master.sv rename to example-selftest/axi_self_test_master.v index e86dc04..8c0d0be 100644 --- a/example-selftest/axi_self_test_master.sv +++ b/example-selftest/axi_self_test_master.v @@ -2,7 +2,7 @@ //-------------------------------------------------------------------------------------------------------- // Module : axi_self_test_master // Type : synthesizable -// Standard: SystemVerilog 2005 (IEEE1800-2005) +// Standard: Verilog 2001 (IEEE1364-2001) // Function: write increase data to AXI4 slave, // then read data and check whether they are increasing //-------------------------------------------------------------------------------------------------------- @@ -39,13 +39,22 @@ module axi_self_test_master #( output reg [ 15:0] error_cnt ); -initial {awaddr, araddr} = '0; -initial {error, error_cnt} = '0; + +initial {awaddr, araddr} = 0; +initial {error, error_cnt} = 0; wire aw_end; -reg awaddr_carry = '0; -reg [7:0] w_cnt = '0; -enum logic [2:0] {INIT, AW, W, B, AR, R} stat = INIT; +reg awaddr_carry = 1'b0; +reg [7:0] w_cnt = 8'd0; + +localparam [2:0] INIT = 3'd0, + AW = 3'd1, + W = 3'd2, + B = 3'd3, + AR = 3'd4, + R = 3'd5; + +reg [2:0] stat = INIT; generate if(A_WIDTH_TEST=8'h30 && rx_byte<=8'h39) || (rx_byte>=8'h41 && rx_byte<=8'h46) || (rx_byte>=8'h61 && rx_byte<=8'h66))); // 0~9, A~F, a~f +wire [ 3:0] rx_hex = (rx_byte>=8'h30 && rx_byte<=8'h39) ? rx_byte[3:0] : (rx_byte[3:0] + 4'd9); + + + +reg rwtype = 1'b0; +reg [A_WIDTH-1:0] addr = 0; +reg [ 8:0] len = 9'h0; +reg wwen = 1'b0; +reg [8*BYTE_WIDTH-1:0] wwdata = 0; +reg [ 7:0] wraddr = 8'h0; + + +localparam [ 3:0] S_IDLE = 4'd0, + S_PARSE_ADDR = 4'd1, + S_PARSE_LEN = 4'd2, + S_PARSE_WDATA = 4'd3, + S_AXI_RADDR = 4'd4, + S_AXI_WADDR = 4'd5, + S_AXI_RDATA = 4'd6, + S_AXI_WDATA = 4'd7, + S_AXI_B = 4'd8, + S_W_DONE = 4'd9, + S_INVALID = 4'd10, + S_FAILED = 4'd11; + +reg [ 3:0] state = S_IDLE; + + +always @ (posedge clk or negedge rstn) + if (~rstn) begin + rwtype <= 1'b0; + addr <= 0; + len <= 9'h0; + wwen <= 1'b0; + wwdata <= 0; + wraddr <= 8'h0; + state <= S_IDLE; + end else begin + case (state) + + S_IDLE : begin + rwtype <= rx_char_w; + addr <= 0; + len <= 9'h0; + wwen <= 1'b0; + wwdata <= 0; + wraddr <= 8'h0; + if (rx_char_w | rx_char_r) + state <= S_PARSE_ADDR; + else if (rx_space | rx_newline) + state <= S_IDLE; + else if (rx_valid) + state <= S_INVALID; + end + + S_PARSE_ADDR : + if (rx_is_hex) begin + addr <= (addr << 4); + addr[3:0] <= rx_hex; + end else if (rx_space) + state <= rwtype ? S_PARSE_WDATA : S_PARSE_LEN; + else if (rx_newline) + state <= S_FAILED; + else if (rx_valid) + state <= S_INVALID; + + S_PARSE_LEN : + if (rx_is_hex) begin + len <= (len << 4); + len[3:0] <= rx_hex; + end else if (rx_newline) begin + len <= (len >= 9'h100) ? 9'hFF : (len == 9'h0) ? 9'h0 : (len - 9'h1); + state <= S_AXI_RADDR; + end else if (rx_space) begin + state <= S_PARSE_LEN; + end else if (rx_valid) begin + state <= S_INVALID; + end + + S_PARSE_WDATA : + if (rx_is_hex) begin + wwen <= 1'b1; + wwdata <= (wwdata << 4); + wwdata[3:0] <= rx_hex; + end else if (rx_space) begin + wwen <= 1'b0; + if (wwen) begin + wwdata <= 0; + len <= len + 9'd1; + end + end else if (rx_newline) begin + if (wwen) begin + state <= ( len < 9'h100) ? S_AXI_WADDR : S_FAILED; + end else begin + state <= (len >= 9'd0 && len <= 9'd100) ? S_AXI_WADDR : S_FAILED; + len <= len - 9'd1; + end + end else if (rx_valid) begin + state <= S_INVALID; + end + + S_AXI_RADDR : + if (arready) + state <= S_AXI_RDATA; + + S_AXI_WADDR : + if (awready) + state <= S_AXI_WDATA; + + S_AXI_RDATA : + if (rvalid) begin + len <= len - 9'd1; + if (rlast || (len==9'd0)) + state <= S_IDLE; + end + + S_AXI_WDATA : + if (wready) begin + wraddr <= wraddr + 8'd1; + if (wraddr >= len[7:0]) + state <= S_AXI_B; + end + + S_AXI_B : + if (bvalid) + state <= S_W_DONE; + + S_W_DONE : + state <= S_IDLE; + + S_INVALID : + if (rx_newline) + state <= S_FAILED; + + default : // S_FAILED : + state <= S_IDLE; + + endcase + end + + +reg [8*BYTE_WIDTH-1:0] wbuf [0:255]; + +always @ (posedge clk) + if ( (state == S_PARSE_WDATA) && (rx_space || rx_newline) && wwen ) + wbuf[len[7:0]] <= wwdata; + +wire [ 7:0] wraddr_next = (wvalid & wready) ? (wraddr + 8'd1) : wraddr; +reg [8*BYTE_WIDTH-1:0] wrdata; + +always @ (posedge clk) + wrdata <= wbuf[wraddr_next]; + + + +assign arvalid = (state == S_AXI_RADDR); +assign araddr = addr; +assign arlen = len[7:0]; + +assign awvalid = (state == S_AXI_WADDR); +assign awaddr = addr; +assign awlen = len[7:0]; + +assign rready = (state == S_AXI_RDATA); + +assign wvalid = (state == S_AXI_WDATA); +assign wlast = (wraddr >= len[7:0]); +assign wdata = wrdata; + +assign bready = 1'b1; // (state == S_AXI_B) + + + + + +function [7:0] toHex; + input [3:0] val; +begin + toHex = (val <= 4'd9) ? {4'h3, val} : {4'd6, 1'b0, val[2:0]-3'h1}; +end +endfunction + + +wire tx_w_done = (state == S_W_DONE); +wire tx_failed = (state == S_FAILED); +wire tx_number = (rvalid & rready); + +wire [8*2*BYTE_WIDTH-1:0] tx_data_failed = 64'h20_64_69_6C_61_76_6E_69; // "invalid" +wire [8*2*BYTE_WIDTH-1:0] tx_data_w_done = 64'h20_20_20_20_79_61_6B_6F; // "okay" +wire [8*2*BYTE_WIDTH-1:0] tx_data_number; + +wire tx_valid = tx_failed | tx_w_done | tx_number; +wire [8*2*BYTE_WIDTH-1:0] tx_data = tx_failed ? tx_data_failed : tx_w_done ? tx_data_w_done : tx_data_number; +wire tx_last = tx_failed ? 1'b1 : tx_w_done ? 1'b1 : (rlast || (len==9'd0)); + + +generate genvar i; + for (i=0; i<2*BYTE_WIDTH; i=i+1) begin : gen_tx_tdata + assign tx_data_number[8*i +: 8] = toHex(rdata[4*(2*BYTE_WIDTH-1-i) +: 4]); + end +endgenerate + + + +uart_tx #( + .CLK_FREQ ( CLK_FREQ ), + .BAUD_RATE ( BAUD_RATE ), + .PARITY ( PARITY ), + .STOP_BITS ( 4 ), + .BYTE_WIDTH ( 2 * BYTE_WIDTH ), + .FIFO_EA ( 9 ), + .EXTRA_BYTE_AFTER_TRANSFER ( " " ), + .EXTRA_BYTE_AFTER_PACKET ( "\n" ) +) u_uart_tx ( + .rstn ( rstn ), + .clk ( clk ), + .i_tready ( ), + .i_tvalid ( tx_valid ), + .i_tdata ( tx_data ), + .i_tkeep ( {(2*BYTE_WIDTH){1'b1}} ), + .i_tlast ( tx_last ), + .o_uart_tx ( o_uart_tx ) +); + + +endmodule diff --git a/example-uart-read-write/uart/uart_rx.v b/example-uart-read-write/uart/uart_rx.v new file mode 100644 index 0000000..66ba82e --- /dev/null +++ b/example-uart-read-write/uart/uart_rx.v @@ -0,0 +1,335 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : uart_rx +// Type : synthesizable, IP's top +// Standard: Verilog 2001 (IEEE1364-2001) +// Function: input UART signal, +// output AXI-stream (configurable byte data width) +//-------------------------------------------------------------------------------------------------------- + +module uart_rx #( + // clock frequency + parameter CLK_FREQ = 50000000, // clk frequency, Unit : Hz + // UART format + parameter BAUD_RATE = 115200, // Unit : Hz + parameter PARITY = "NONE", // "NONE", "ODD", or "EVEN" + // RX fifo depth + parameter FIFO_EA = 0 // 0:no fifo 1,2:depth=4 3:depth=8 4:depth=16 ... 10:depth=1024 11:depth=2048 ... +) ( + input wire rstn, + input wire clk, + // UART RX input signal + input wire i_uart_rx, + // output AXI-stream master. Associated clock = clk. + input wire o_tready, + output reg o_tvalid, + output reg [ 7:0] o_tdata, + // report whether there's a overflow + output reg o_overflow +); + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Generate fractional precise upper limit for counter +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +localparam BAUD_CYCLES = ( (CLK_FREQ*10*2 + BAUD_RATE) / (BAUD_RATE*2) ) / 10 ; +localparam BAUD_CYCLES_FRAC = ( (CLK_FREQ*10*2 + BAUD_RATE) / (BAUD_RATE*2) ) % 10 ; + +localparam HALF_BAUD_CYCLES = BAUD_CYCLES / 2; +localparam THREE_QUARTER_BAUD_CYCLES = (BAUD_CYCLES*3) / 4; + +localparam [9:0] ADDITION_CYCLES = (BAUD_CYCLES_FRAC == 0) ? 10'b0000000000 : + (BAUD_CYCLES_FRAC == 1) ? 10'b0000010000 : + (BAUD_CYCLES_FRAC == 2) ? 10'b0010000100 : + (BAUD_CYCLES_FRAC == 3) ? 10'b0010010010 : + (BAUD_CYCLES_FRAC == 4) ? 10'b0101001010 : + (BAUD_CYCLES_FRAC == 5) ? 10'b0101010101 : + (BAUD_CYCLES_FRAC == 6) ? 10'b1010110101 : + (BAUD_CYCLES_FRAC == 7) ? 10'b1101101101 : + (BAUD_CYCLES_FRAC == 8) ? 10'b1101111011 : + /*BAUD_CYCLES_FRAC == 9)*/ 10'b1111101111 ; + +wire [31:0] cycles [9:0]; + +assign cycles[0] = BAUD_CYCLES + (ADDITION_CYCLES[0] ? 1 : 0); +assign cycles[1] = BAUD_CYCLES + (ADDITION_CYCLES[1] ? 1 : 0); +assign cycles[2] = BAUD_CYCLES + (ADDITION_CYCLES[2] ? 1 : 0); +assign cycles[3] = BAUD_CYCLES + (ADDITION_CYCLES[3] ? 1 : 0); +assign cycles[4] = BAUD_CYCLES + (ADDITION_CYCLES[4] ? 1 : 0); +assign cycles[5] = BAUD_CYCLES + (ADDITION_CYCLES[5] ? 1 : 0); +assign cycles[6] = BAUD_CYCLES + (ADDITION_CYCLES[6] ? 1 : 0); +assign cycles[7] = BAUD_CYCLES + (ADDITION_CYCLES[7] ? 1 : 0); +assign cycles[8] = BAUD_CYCLES + (ADDITION_CYCLES[8] ? 1 : 0); +assign cycles[9] = BAUD_CYCLES + (ADDITION_CYCLES[9] ? 1 : 0); + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// Input beat +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +reg rx_d1 = 1'b0; + +always @ (posedge clk or negedge rstn) + if (~rstn) + rx_d1 <= 1'b0; + else + rx_d1 <= i_uart_rx; + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// count continuous '1' +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +reg [31:0] count1 = 0; + +always @ (posedge clk or negedge rstn) + if (~rstn) begin + count1 <= 0; + end else begin + if (rx_d1) + count1 <= (count1 < 'hFFFFFFFF) ? (count1 + 1) : count1; + else + count1 <= 0; + end + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// main FSM +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +localparam [ 3:0] TOTAL_BITS_MINUS1 = (PARITY == "ODD" || PARITY == "EVEN") ? 4'd9 : 4'd8; + +localparam [ 1:0] S_IDLE = 2'd0 , + S_RX = 2'd1 , + S_STOP_BIT = 2'd2 ; + +reg [ 1:0] state = S_IDLE; +reg [ 8:0] rxbits = 9'b0; +reg [ 3:0] rxcnt = 4'd0; +reg [31:0] cycle = 1; +reg [32:0] countp = 33'h1_0000_0000; // countp>=0x100000000 means '1' is majority , countp<0x100000000 means '0' is majority +wire rxbit = countp[32]; // countp>=0x100000000 corresponds to countp[32]==1, countp<0x100000000 corresponds to countp[32]==0 + +wire [ 7:0] rbyte = (PARITY == "ODD" ) ? rxbits[7:0] : + (PARITY == "EVEN") ? rxbits[7:0] : + /*(PARITY == "NONE")*/ rxbits[8:1] ; + +wire parity_correct = (PARITY == "ODD" ) ? ((~(^(rbyte))) == rxbits[8]) : + (PARITY == "EVEN") ? ( (^(rbyte)) == rxbits[8]) : + /*(PARITY == "NONE")*/ 1'b1 ; + + +always @ (posedge clk or negedge rstn) + if (~rstn) begin + state <= S_IDLE; + rxbits <= 9'b0; + rxcnt <= 4'd0; + cycle <= 1; + countp <= 33'h1_0000_0000; + end else begin + case (state) + S_IDLE : begin + if ((count1 >= THREE_QUARTER_BAUD_CYCLES) && (rx_d1 == 1'b0)) // receive a '0' which is followed by continuous '1' for half baud cycles + state <= S_RX; + rxcnt <= 4'd0; + cycle <= 2; // we've already receive a '0', so here cycle = 2 + countp <= (33'h1_0000_0000 - 33'd1); // we've already receive a '0', so here countp = initial_value - 1 + end + + S_RX : + if ( cycle < cycles[rxcnt] ) begin // cycle loop from 1 to cycles[rxcnt] + cycle <= cycle + 1; + countp <= rx_d1 ? (countp + 33'd1) : (countp - 33'd1); + end else begin + cycle <= 1; // reset counter + countp <= 33'h1_0000_0000; // reset counter + + if ( rxcnt < TOTAL_BITS_MINUS1 ) begin // rxcnt loop from 0 to TOTAL_BITS_MINUS1 + rxcnt <= rxcnt + 4'd1; + if ((rxcnt == 4'd0) && (rxbit == 1'b1)) // except start bit, but get '1' + state <= S_IDLE; // RX failed, back to IDLE + end else begin + rxcnt <= 4'd0; + state <= S_STOP_BIT; + end + + rxbits <= {rxbit, rxbits[8:1]}; // put current rxbit to MSB of rxbits, and right shift other bits + end + + default : // S_STOP_BIT + if ( cycle < THREE_QUARTER_BAUD_CYCLES) begin // cycle loop from 1 to THREE_QUARTER_BAUD_CYCLES + cycle <= cycle + 1; + end else begin + cycle <= 1; // reset counter + state <= S_IDLE; // back to IDLE + end + endcase + end + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// RX result byte +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +reg f_tvalid = 1'b0; +reg [7:0] f_tdata = 8'h0; + +always @ (posedge clk or negedge rstn) + if (~rstn) begin + f_tvalid <= 1'b0; + f_tdata <= 8'h0; + end else begin + f_tvalid <= 1'b0; + f_tdata <= 8'h0; + if (state == S_STOP_BIT) begin + if ( cycle < THREE_QUARTER_BAUD_CYCLES) begin + end else begin + if ((count1 >= HALF_BAUD_CYCLES) && parity_correct) begin // stop bit have enough '1', and parity correct + f_tvalid <= 1'b1; + f_tdata <= rbyte; // received a correct byte, output it + end + end + end + end + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// RX fifo +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +wire f_tready; + +generate if (FIFO_EA <= 0) begin // no RX fifo + + assign f_tready = o_tready; + always @ (*) o_tvalid = f_tvalid; + always @ (*) o_tdata = f_tdata; + +end else begin // TX fifo + + localparam EA = (FIFO_EA <= 2) ? 2 : FIFO_EA; + + reg [7:0] buffer [ ((1<= 0) ? (BAUD_CYCLES + (ADDITION_CYCLES[0] ? 1 : 0)) : 0 ) + + ( (index >= 1) ? (BAUD_CYCLES + (ADDITION_CYCLES[1] ? 1 : 0)) : 0 ) + + ( (index >= 2) ? (BAUD_CYCLES + (ADDITION_CYCLES[2] ? 1 : 0)) : 0 ) + + ( (index >= 3) ? (BAUD_CYCLES + (ADDITION_CYCLES[3] ? 1 : 0)) : 0 ) + + ( (index >= 4) ? (BAUD_CYCLES + (ADDITION_CYCLES[4] ? 1 : 0)) : 0 ) + + ( (index >= 5) ? (BAUD_CYCLES + (ADDITION_CYCLES[5] ? 1 : 0)) : 0 ) + + ( (index >= 6) ? (BAUD_CYCLES + (ADDITION_CYCLES[6] ? 1 : 0)) : 0 ) + + ( (index >= 7) ? (BAUD_CYCLES + (ADDITION_CYCLES[7] ? 1 : 0)) : 0 ) + + ( (index >= 8) ? (BAUD_CYCLES + (ADDITION_CYCLES[8] ? 1 : 0)) : 0 ) + + ( (index >= 9) ? (BAUD_CYCLES + (ADDITION_CYCLES[9] ? 1 : 0)) : 0 ) ; + + localparam real ideal_time_ns = ((index+1)*1000000000.0/BAUD_RATE); + localparam real actual_time_ns = (cycles_acc*1000000000.0/CLK_FREQ); + localparam real uncertainty = (1000000000.0/CLK_FREQ); + localparam real error = ( (ideal_time_ns>actual_time_ns) ? (ideal_time_ns-actual_time_ns) : (-ideal_time_ns+actual_time_ns) ) + uncertainty; + localparam real relative_error_percent = (error / (1000000000.0/BAUD_RATE)) * 100.0; + + initial if (PARITY == "ODD" || PARITY == "EVEN" || index < 9) begin + $display("uart_rx : t%-2d- t0 = %.0f ns (ideal) %.0f +- %.0f ns (actual). error=%.0f ns relative_error=%.3f%%" , + (index+1) , + ideal_time_ns , + actual_time_ns, + uncertainty, + error, + relative_error_percent + ); + + if ( relative_error_percent > 8.0 ) begin $error("relative_error is too large"); $stop; end // if relative error larger than 8% + end + end +endgenerate + + +endmodule diff --git a/example-uart-read-write/uart/uart_tx.v b/example-uart-read-write/uart/uart_tx.v new file mode 100644 index 0000000..f07d505 --- /dev/null +++ b/example-uart-read-write/uart/uart_tx.v @@ -0,0 +1,340 @@ + +//-------------------------------------------------------------------------------------------------------- +// Module : uart_tx +// Type : synthesizable, IP's top +// Standard: Verilog 2001 (IEEE1364-2001) +// Function: input AXI-stream (configurable data width), +// output UART signal +//-------------------------------------------------------------------------------------------------------- + +module uart_tx #( + // clock frequency + parameter CLK_FREQ = 50000000, // clk frequency, Unit : Hz + // UART format + parameter BAUD_RATE = 115200, // Unit : Hz + parameter PARITY = "NONE", // "NONE", "ODD", or "EVEN" + parameter STOP_BITS = 2, // can be 1, 2, 3, 4, ... + // AXI stream data width + parameter BYTE_WIDTH = 1, // can be 1, 2, 3, 4, ... + // TX fifo depth + parameter FIFO_EA = 0, // 0:no fifo 1,2:depth=4 3:depth=8 4:depth=16 ... 10:depth=1024 11:depth=2048 ... + // do you want to send extra byte after each AXI-stream transfer or packet? + parameter EXTRA_BYTE_AFTER_TRANSFER = "", // specify a extra byte to send after each AXI-stream transfer. when ="", do not send this extra byte + parameter EXTRA_BYTE_AFTER_PACKET = "" // specify a extra byte to send after each AXI-stream packet . when ="", do not send this extra byte +) ( + input wire rstn, + input wire clk, + // input stream : AXI-stream slave. Associated clock = clk + output wire i_tready, + input wire i_tvalid, + input wire [8*BYTE_WIDTH-1:0] i_tdata, + input wire [ BYTE_WIDTH-1:0] i_tkeep, + input wire i_tlast, + // UART TX output signal + output reg o_uart_tx +); + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// TX fifo +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +wire f_tready; +reg f_tvalid; +reg [8*BYTE_WIDTH-1:0] f_tdata; +reg [ BYTE_WIDTH-1:0] f_tkeep; +reg f_tlast; + +generate if (FIFO_EA <= 0) begin // no TX fifo + + assign i_tready = f_tready; + always @ (*) f_tvalid = i_tvalid; + always @ (*) f_tdata = i_tdata; + always @ (*) f_tkeep = i_tkeep; + always @ (*) f_tlast = i_tlast; + +end else begin // TX fifo + + localparam EA = (FIFO_EA<=2) ? 2 : FIFO_EA; + localparam DW = ( 1 + BYTE_WIDTH + 8*BYTE_WIDTH ); // 1-bit tlast, (BYTE_WIDTH)-bit tkeep, (8*BYTE_WIDTH)-bit tdata + + reg [DW-1:0] buffer [ ((1<= ('hFFFFFFFF-9-PARITY_BITS)) ? 'hFFFFFFFF : (PARITY_BITS+STOP_BITS+9); + +localparam [ 0:0] BYTE_T_EN = (EXTRA_BYTE_AFTER_TRANSFER == "") ? 1'b0 : 1'b1; +localparam [ 0:0] BYTE_B_EN = (EXTRA_BYTE_AFTER_PACKET == "") ? 1'b0 : 1'b1; +localparam [ 7:0] BYTE_T = EXTRA_BYTE_AFTER_TRANSFER; +localparam [ 7:0] BYTE_P = EXTRA_BYTE_AFTER_PACKET; + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// function for calculate parity bit +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +function [0:0] get_parity; + input [7:0] data; +begin + get_parity = (PARITY == "ODD" ) ? (~(^(data[7:0]))) : + (PARITY == "EVEN") ? (^(data[7:0])) : + /*(PARITY == "NONE")*/ 1'b1 ; +end +endfunction + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// main FSM +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +localparam [ 1:0] S_IDLE = 2'b01 , // only in state S_IDLE, state[0]==1, the goal is to make f_tready pure register-out + S_PREPARE = 2'b00 , + S_TX = 2'b10 ; + +reg [ 1:0] state = S_IDLE; // FSM state register + +reg [8*BYTE_WIDTH-1:0] data = 0; +reg [ BYTE_WIDTH-1:0] keep = 0; +reg byte_t_en = 1'b0; +reg byte_p_en = 1'b0; +reg [ 9:0] txbits = 10'b0; +reg [ 31:0] txcnt = 0; +reg [ 31:0] cycle = 1; + + +always @ (posedge clk or negedge rstn) + if (~rstn) begin + state <= S_IDLE; + data <= 0; + keep <= 0; + byte_t_en <= 1'b0; + byte_p_en <= 1'b0; + txbits <= 10'b0; + txcnt <= 0; + cycle <= 1; + end else begin + case (state) + S_IDLE : begin + state <= f_tvalid ? S_PREPARE : S_IDLE; + data <= f_tdata; + keep <= f_tkeep; + byte_t_en <= BYTE_T_EN; + byte_p_en <= BYTE_B_EN & f_tlast; + txbits <= 10'b0; + txcnt <= 0; + cycle <= 1; + end + + S_PREPARE : begin + data <= (data >> 8); + keep <= (keep >> 1); + if ( keep[0] == 1'b1 ) begin + txbits <= {get_parity(data[7:0]), data[7:0], 1'b0}; + state <= S_TX; + end else if ( keep != ZERO_KEEP ) begin + state <= S_PREPARE; + end else if ( byte_t_en ) begin + byte_t_en <= 1'b0; + txbits <= {get_parity(BYTE_T), BYTE_T, 1'b0}; + state <= S_TX; + end else if ( byte_p_en ) begin + byte_p_en <= 1'b0; + txbits <= {get_parity(BYTE_P), BYTE_P, 1'b0}; + state <= S_TX; + end else begin + state <= S_IDLE; + end + txcnt <= 0; + cycle <= 1; + end + + default : begin // S_TX + if (keep[0] == 1'b0) begin + data <= (data >> 8); + keep <= (keep >> 1); + end + if ( cycle < ((txcnt<=9) ? cycles[txcnt] : STOP_BIT_CYCLES) ) begin // cycle loop from 1 to ((txcnt<=9) ? cycles[txcnt] : STOP_BIT_CYCLES) + cycle <= cycle + 1; + end else begin + cycle <= 1; + txbits <= {1'b1, txbits[9:1]}; // right shift txbits, and fill '1' to MSB + if ( txcnt < (TOTAL_BITS-1) ) begin // txcnt loop from 0 to (TOTAL_BITS-1) + txcnt <= txcnt + 1; + end else begin + txcnt <= 0; + state <= S_PREPARE; + end + end + end + endcase + end + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// generate UART output +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +initial o_uart_tx = 1'b1; + +always @ (posedge clk or negedge rstn) + if (~rstn) + o_uart_tx <= 1'b1; + else + o_uart_tx <= (state == S_TX) ? txbits[0] : 1'b1; + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// generate AXI-stream TREADY +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +assign f_tready = state[0]; // (state == S_IDLE) + + + +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +// parameter checking +//--------------------------------------------------------------------------------------------------------------------------------------------------------------- +initial begin + if (BYTE_WIDTH <= 0) begin $error("invalid parameter : BYTE_WIDTH<=0"); $stop; end + if (STOP_BITS <= 0) begin $error("invalid parameter : STOP_BITS <=0"); $stop; end + if (BAUD_CYCLES < 1) begin $error("invalid parameter : BAUD_CYCLES < 1, please use a faster driving clock"); $stop; end + + $display("uart_tx : parity = %s" , PARITY ); + $display("uart_tx : clock period = %.0f ns (%-10d Hz)" , 1000000000.0/CLK_FREQ , CLK_FREQ ); + $display("uart_tx : baud rate period = %.0f ns (%-10d Hz)" , 1000000000.0/BAUD_RATE , BAUD_RATE); + $display("uart_tx : baud cycles = %-10d" , BAUD_CYCLES ); + $display("uart_tx : baud cycles frac = %-10d" , BAUD_CYCLES_FRAC ); + + if (PARITY == "ODD" || PARITY == "EVEN") begin + $display("uart_tx : __ ____ ____ ____ ____ ____ ____ ____ ____________ "); + $display("uart_tx : wave \\____/____X____X____X____X____X____X____X____X____/ "); + $display("uart_tx : bits | S | B0 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | P | "); + $display("uart_tx : time_points t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 t10 "); + $display("uart_tx :"); + end else begin + $display("uart_tx : __ ____ ____ ____ ____ ____ ____ ____ _______ "); + $display("uart_tx : wave \\____/____X____X____X____X____X____X____X____/ "); + $display("uart_tx : bits | S | B0 | B1 | B2 | B3 | B4 | B5 | B6 | B7 | "); + $display("uart_tx : time_points t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 "); + $display("uart_tx :"); + end +end + +generate genvar index, i; + for (index=0; index<=9; index=index+1) begin : print_and_check_time + localparam cycles_acc = ( (index >= 0) ? (BAUD_CYCLES + (ADDITION_CYCLES[0] ? 1 : 0)) : 0 ) + + ( (index >= 1) ? (BAUD_CYCLES + (ADDITION_CYCLES[1] ? 1 : 0)) : 0 ) + + ( (index >= 2) ? (BAUD_CYCLES + (ADDITION_CYCLES[2] ? 1 : 0)) : 0 ) + + ( (index >= 3) ? (BAUD_CYCLES + (ADDITION_CYCLES[3] ? 1 : 0)) : 0 ) + + ( (index >= 4) ? (BAUD_CYCLES + (ADDITION_CYCLES[4] ? 1 : 0)) : 0 ) + + ( (index >= 5) ? (BAUD_CYCLES + (ADDITION_CYCLES[5] ? 1 : 0)) : 0 ) + + ( (index >= 6) ? (BAUD_CYCLES + (ADDITION_CYCLES[6] ? 1 : 0)) : 0 ) + + ( (index >= 7) ? (BAUD_CYCLES + (ADDITION_CYCLES[7] ? 1 : 0)) : 0 ) + + ( (index >= 8) ? (BAUD_CYCLES + (ADDITION_CYCLES[8] ? 1 : 0)) : 0 ) + + ( (index >= 9) ? (BAUD_CYCLES + (ADDITION_CYCLES[9] ? 1 : 0)) : 0 ) ; + + localparam real ideal_time_ns = ((index+1)*1000000000.0/BAUD_RATE); + localparam real actual_time_ns = (cycles_acc*1000000000.0/CLK_FREQ); + localparam real error = (ideal_time_ns>actual_time_ns) ? (ideal_time_ns-actual_time_ns) : (-ideal_time_ns+actual_time_ns); + localparam real relative_error_percent = (error / (1000000000.0/BAUD_RATE)) * 100.0; + + initial if (PARITY == "ODD" || PARITY == "EVEN" || index < 9) begin + $display("uart_tx : t%-2d- t0 = %.0f ns (ideal) %.0f ns (actual). error=%.0f ns relative_error=%.3f%%" , + (index+1) , + ideal_time_ns , + actual_time_ns, + error, + relative_error_percent + ); + + if ( relative_error_percent > 3.0 ) begin $error("relative_error is too large"); $stop; end // if relative error larger than 3% + end + end +endgenerate + + +endmodule diff --git a/example-uart-read-write/uart2axi4.sv b/example-uart-read-write/uart2axi4.sv deleted file mode 100644 index c0ddc04..0000000 --- a/example-uart-read-write/uart2axi4.sv +++ /dev/null @@ -1,401 +0,0 @@ - -//-------------------------------------------------------------------------------------------------------- -// Module : uart2axi4 -// Type : synthesizable -// Standard: SystemVerilog 2005 (IEEE1800-2005) -// Function: convert UART command to AXI4 read/write action -//-------------------------------------------------------------------------------------------------------- - -module uart2axi4 #( - parameter A_WIDTH = 26, - parameter D_WIDTH = 16 -) ( - input wire rstn, - input wire clk, - - output wire awvalid, - input wire awready, - output reg [A_WIDTH-1:0] awaddr, - output reg [ 7:0] awlen, - output wire wvalid, - input wire wready, - output wire wlast, - output wire [D_WIDTH-1:0] wdata, - input wire bvalid, - output wire bready, - output wire arvalid, - input wire arready, - output reg [A_WIDTH-1:0] araddr, - output reg [ 7:0] arlen, - input wire rvalid, - output wire rready, - input wire rlast, - input wire [D_WIDTH-1:0] rdata, - - input wire uart_rx, - output wire uart_tx -); - -function automatic logic isW(input [7:0] c); - return c==8'h57 || c==8'h77; -endfunction - -function automatic logic isR(input [7:0] c); - return c==8'h52 || c==8'h72; -endfunction - -function automatic logic isSpace(input [7:0] c); - return c==8'h20 || c==8'h09; -endfunction - -function automatic logic isNewline(input [7:0] c); - return c==8'h0D || c==8'h0A; -endfunction - -function automatic logic [3:0] isHex(input [7:0] c); - if(c>=8'h30 && c<= 8'h39) - return 1'b1; - else if(c>=8'h41 && c<=8'h46 || c>=8'h61 && c<=8'h66) - return 1'b1; - else - return 1'b0; -endfunction - -function automatic logic [3:0] getHex(input [7:0] c); - if(c>=8'h30 && c<= 8'h39) - return c[3:0]; - else if(c>=8'h41 && c<=8'h46 || c>=8'h61 && c<=8'h66) - return c[3:0] + 8'h9; - else - return 4'd0; -endfunction - -localparam V_WIDTH = A_WIDTH>D_WIDTH ? (A_WIDTH>8?A_WIDTH:8) : (D_WIDTH>8?D_WIDTH:8); - -wire rx_valid; -wire [7:0] rx_data; - -reg rw; // 0:write 1:read -reg [V_WIDTH-1:0] value; -reg [ 7:0] value_cnt; -reg wbuf_wen; -reg [ 7:0] wbuf_waddr; -reg [D_WIDTH-1:0] wbuf_wdata; -reg [ 7:0] wbuf_raddr; -reg [D_WIDTH-1:0] wbuf_rdata; -enum logic [3:0] {IDLE, INVALID, GADDR, GRLEN, GWDATA, AXI_AR, AXI_R, AXI_AW, AXI_W, AXI_B} stat; -wire[ 7:0] wbuf_raddr_n = stat == AXI_W && wready ? wbuf_raddr + 8'd1 : wbuf_raddr; - -assign awvalid = stat == AXI_AW; -assign wvalid = stat == AXI_W; -assign wlast = wbuf_raddr == wbuf_waddr; -assign wdata = wbuf_rdata; -assign bready = stat == AXI_B; -assign arvalid = stat == AXI_AR; - -always @ (posedge clk or negedge rstn) - if(~rstn) begin - awaddr <= '0; - awlen <= '0; - araddr <= '0; - arlen <= '0; - rw <= 1'b0; - value <= '0; - value_cnt <= '0; - wbuf_wen <= 1'b0; - wbuf_waddr <= '0; - wbuf_wdata <= '0; - wbuf_raddr <= '0; - stat <= IDLE; - end else begin - wbuf_wen <= 1'b0; - case(stat) - IDLE: if(rx_valid) begin - value <= '0; - value_cnt <= '0; - wbuf_raddr <= '0; - if( isW(rx_data) ) begin - rw <= 1'b0; - stat <= GADDR; - end else if( isR(rx_data) ) begin - rw <= 1'b1; - stat <= GADDR; - end else if( ~isNewline(rx_data) ) begin - stat <= INVALID; - end - end - GADDR: if(rx_valid) begin - if( isNewline(rx_data) ) begin - value <= '0; - stat <= IDLE; - end else if( isSpace(rx_data) ) begin - value <= '0; - if(rw) begin - araddr <= value[A_WIDTH-1:0]; - stat <= GRLEN; - end else begin - awaddr <= value[A_WIDTH-1:0]; - stat <= GWDATA; - end - end else if( isHex(rx_data) ) begin - value <= { value[V_WIDTH-5:0], getHex(rx_data) }; - end else begin - stat <= INVALID; - end - end - GRLEN: if(rx_valid) begin - if( isNewline(rx_data) ) begin - value <= '0; - arlen <= value[7:0]; - stat <= AXI_AR; - end else if( isHex(rx_data) ) begin - value <= { value[V_WIDTH-5:0], getHex(rx_data) }; - end else begin - stat <= INVALID; - end - end - GWDATA: if(rx_valid) begin - if( isNewline(rx_data) ) begin - wbuf_wen <= 1'b1; - wbuf_waddr <= value_cnt; - wbuf_wdata <= value[D_WIDTH-1:0]; - awlen <= value_cnt; - stat <= AXI_AW; - end else if( isSpace(rx_data) ) begin - value <= '0; - value_cnt <= value_cnt + 8'd1; - wbuf_wen <= 1'b1; - wbuf_waddr <= value_cnt; - wbuf_wdata <= value[D_WIDTH-1:0]; - end else if( isHex(rx_data) ) begin - value <= { value[V_WIDTH-5:0], getHex(rx_data) }; - end else begin - stat <= INVALID; - end - end - INVALID: if( rx_valid ) begin - if ( isNewline(rx_data) ) - stat <= IDLE; - end - AXI_AR: if(arready) begin - stat <= AXI_R; - end - AXI_R: if(rvalid & rready & rlast) begin - stat <= IDLE; - end - AXI_AW: if(awready) begin - stat <= AXI_W; - end - AXI_W: if(wready) begin - wbuf_raddr <= wbuf_raddr + 8'd1; - if(wbuf_raddr==awlen) - stat <= AXI_B; - end - AXI_B: if(bvalid) begin - stat <= IDLE; - end - default: stat<=IDLE; - endcase - end - - - -logic [D_WIDTH-1:0] mem_for_axi4write [256]; - -always @ (posedge clk) - wbuf_rdata <= mem_for_axi4write[wbuf_raddr_n]; - -always @ (posedge clk) - if(wbuf_wen) - mem_for_axi4write[wbuf_waddr] <= wbuf_wdata; - - - -localparam CLK_DIV = 162; -localparam CLK_PART = 6; - -reg uart_rx_done = 1'b0; -reg [ 7:0] uart_rx_data = 8'h0; -reg [ 2:0] uart_rx_supercnt=3'h0; -reg [31:0] uart_rx_cnt = 0; -reg [ 7:0] uart_rx_databuf = 8'h0; -reg [ 5:0] uart_rx_status=6'h0, uart_rx_shift=6'h0; -reg uart_rxr=1'b1; -wire recvbit = (uart_rx_shift[1]&uart_rx_shift[0]) | (uart_rx_shift[0]&uart_rxr) | (uart_rxr&uart_rx_shift[1]) ; -wire [2:0] supercntreverse = {uart_rx_supercnt[0], uart_rx_supercnt[1], uart_rx_supercnt[2]}; - -assign rx_valid = uart_rx_done; -assign rx_data = uart_rx_data; - -always @ (posedge clk or negedge rstn) - if(~rstn) - uart_rxr <= 1'b1; - else - uart_rxr <= uart_rx; - -always @ (posedge clk or negedge rstn) - if(~rstn) begin - uart_rx_done <= 1'b0; - uart_rx_data <= 8'h0; - uart_rx_supercnt<= 3'h0; - uart_rx_status <= 6'h0; - uart_rx_shift <= 6'h0; - uart_rx_databuf <= 8'h0; - uart_rx_cnt <= 0; - end else begin - uart_rx_done <= 1'b0; - if( (supercntreverse=CLK_DIV) : (uart_rx_cnt>=CLK_DIV-1) ) begin - if(uart_rx_status==0) begin - if(uart_rx_shift == 6'b111_000) - uart_rx_status <= 1; - end else begin - if(uart_rx_status[5] == 1'b0) begin - if(uart_rx_status[1:0] == 2'b11) - uart_rx_databuf <= {recvbit, uart_rx_databuf[7:1]}; - uart_rx_status <= uart_rx_status + 5'b1; - end else begin - if(uart_rx_status<62) begin - uart_rx_status <= 62; - uart_rx_data <= uart_rx_databuf; - uart_rx_done <= 1'b1; - end else begin - uart_rx_status <= uart_rx_status + 6'd1; - end - end - end - uart_rx_shift <= {uart_rx_shift[4:0], uart_rxr}; - uart_rx_supercnt <= uart_rx_supercnt + 3'h1; - uart_rx_cnt <= 0; - end else - uart_rx_cnt <= uart_rx_cnt + 1; - end - - - -axis2uarttx #( - .CLK_DIV ( 651 ), - .DATA_WIDTH ( D_WIDTH ), - .FIFO_ASIZE ( 10 ) -) uart_tx_i ( - .rstn ( rstn ), - .clk ( clk ), - .tvalid ( rvalid ), - .tready ( rready ), - .tlast ( rlast ), - .tdata ( rdata ), - .uart_tx ( uart_tx ) -); - -endmodule - - - - - - -module axis2uarttx #( - parameter CLK_DIV = 434, - parameter DATA_WIDTH = 32, - parameter FIFO_ASIZE = 8 -) ( - // AXI-stream (slave) side - input logic rstn, - input logic clk, - input logic tvalid, - input logic tlast, - output logic tready, - input logic [DATA_WIDTH-1:0] tdata, - // UART TX signal - output logic uart_tx -); -localparam TX_WIDTH = (DATA_WIDTH+3) / 4; - -function automatic logic [7:0] hex2ascii (input [3:0] hex); - return {4'h3, hex} + ((hex<4'hA) ? 8'h0 : 8'h7) ; -endfunction - -logic uart_txb; -logic [FIFO_ASIZE-1:0] fifo_rpt='0, fifo_wpt='0; -wire [FIFO_ASIZE-1:0] fifo_wpt_next = fifo_wpt + {{(FIFO_ASIZE-1){1'b0}}, 1'b1}; -wire [FIFO_ASIZE-1:0] fifo_rpt_next = fifo_rpt + {{(FIFO_ASIZE-1){1'b0}}, 1'b1}; -logic [31:0] cyccnt=0, hexcnt=0, txcnt=0; -logic [ 7:0] txshift = '1; -logic fifo_tlast; -logic [DATA_WIDTH-1:0] fifo_data; -logic endofline = 1'b0; -logic [TX_WIDTH*4-1:0] data='0; -wire emptyn = (fifo_rpt != fifo_wpt); -assign tready = (fifo_rpt != fifo_wpt_next) & rstn; - -always @ (posedge clk or negedge rstn) - if(~rstn) - uart_tx <= 1'b1; - else begin - uart_tx <= uart_txb; - end - -always @ (posedge clk or negedge rstn) - if(~rstn) - fifo_wpt <= '0; - else begin - if(tvalid & tready) fifo_wpt <= fifo_wpt_next; - end - -always @ (posedge clk or negedge rstn) - if(~rstn) - cyccnt <= 0; - else - cyccnt <= (cyccnt1) - txshift <= hex2ascii(data[(hexcnt-2)*4+:4]); - else if(endofline) - txshift <= 8'h0A; - else - txshift <= 8'h20; - txcnt <= 11; - end - end - end else if(emptyn) begin - uart_txb <= 1'b1; - hexcnt <= 2 + TX_WIDTH; - txcnt <= 0; - fifo_rpt <= fifo_rpt_next; - end - end - - -logic [DATA_WIDTH:0] mem_for_axi_stream_to_uart_tx_fifo [1<U-LVu{zMMQe#~ zB(wkleDOKYIp^N{<=zkX)Bf$f*37J#S+myu&rFQ2whGx@`nxzdIAm(7iuyP>cYfjE z;8x?~-F`VvB9yp&JkixOR6-ve_4N-{Rz|$e{e}ykF9*qM>*|Vml;FsK?YZs{kci(- zOexAy$H8HNt0^iNf|d|DP#|pRW6$QctyeRC01k3D0nat+TU<%AV}4cwUBKe>TO3Xy zftUn6v7(fd!dBEIy)%vE_J?ZlBK3nu^}wgkUuXXQIj3@LP^<`x&7(4cDt$u1MjogNCYEO?|>g`9>!w)2K%t&;JT*YI-q41 zB(g37V|7$}bCrq?zCtm*4s-34Y41pf5@kS4T8x`+g73+blN+CtUInOPSIcwmv}oSE zkuI6yKeU*&k!%I7Sujm60#PGO*-h^}zT}O%g`sYOR-q zp;?Fa+hhQ8`8oJwq6IyEkMN7K-dxJJ%KIta9cj#_1FkcMtxNjLk zAe=ZK3dB~G76wxmydx&DC$&Gzc1Ta{q_@;F9trHw7j_zcp3%tB0^V1-prtasd z=jyG}@VAXrf5JorxwzGdEhrUC&lRb7LHNS;b?w1GOAoA`a5FF@h++y)I2MeYm@Xnk zR@=R@SR_hqIQsx*u3ls5Fuc3UQuFamNAU9L5BQvo#?#+;l8Jj7q50H!v>aWt7m!9B zjeVr76~*CT>wO-HErmxq{quw%yuJ_OGKthAQV~s>Hm=UDWPyn;d{QaMD#`YjK-RgOYIluBQO;oAYcWG=>(={~7mU{o2 z^GNL6>?b60pZ@^IMjqy6bA9@lH?21#av zl!v(_Z-T3nVlecGP(z%noTYH_x8pBy>nbCDzutC~Triu+k-4F9bVc}lq7kB(V?76>vC>;R|nD^l--`PJ* z75^MQ2nvL4nrozr)x}L{m&E7C9jVoy@FpSBER({QrRI-)q*usviuh(|1}#KiWS`Do z&BMOytdOlFQCM&?ym0q~09^AipaIo=>S+wt*R)HXq>13@Esm2-mCN{DA64d&*1{vJ z#>k~UkUe10Vbo_CIhl5|8Z(=;3rvng8=o5Q&nnmWN17J{8%3u=3z@dbT$E=cf3kv@ z4bVA;D?0NSF+%{4jzj2~GMuVbK(Bxp8Hr+IISk&%k3A*0l0MZL{*08vT~fFp{N}+r zL1Wxyr9Yc5Vn_u&G-2rei)H8LCo!l)S=uo_?q_D}<};**oBkzvI!`6y`ZYeJSs8QlAYwRiE7Gj46G+&p-KwN}iZkHz)?e>1*g$S&CyJaqIEU6A0FKt?VNm%nD`v&>km7X>nLbj`vvzCjFA2_*)@y)C%?X_tYRA=!-~ zq;hZJge5=j=q$defsmozC(zFEs1}x3LBFEnt@(R2f{igw4&Pf3ZZAw~qJ2T=RO8Zq z&XvDN0U$T1q_v)3+^)=c?J!5$aK&hNip8$eCb0)uW5fvcOnWRVu_MK9*gNk zm^UW=o=@M&fHK3k$_s);Ps3wQld0hID}Z@USjL|i-M@Ce^iMnY0RF%hQ|_iH69Q-H zPUU4pn@N@J9=2{E-hMyv6YdD4Snd{YK6ZVa4m7k;hWbgb)x^8O$Ea5L`U6(a9!t3d z)JGQ~QYJwR2YtA{fHI0?izdG{$e?e$xD4Yg2R2~Cy`4jb)L}9@v`ZmCZu*AlJA%U5 zT&l(8c{OkP(ipk1dY8| z`M}$Za6RUv)BQB5y-9i#LpY7l%2yP|lTLNm1izj8xos0KzJ=5o_o(i%y?0gns3`FH zdJ^AWrD#h%ZJN`V>LY~8uX%!d1*lTNi`OF+3PLkoNT=opwNO}TeTm4*C&pYeaBhz1 z`>gTZI@vUgbEQy--5@9_DIV8>1jr~5k_u&*7+psgai3HJiEo0Ut}sGI4hFWDvn=sA zs;>#gB!tR6*g@Od7Zic)6^(QmCdoBfk}V=^mDEms)#mZi z&;5XUSopg>LppgIsQgjdd#H92A9UzAyZUSCnb(Gub8rBB)E{{Vjyf{Z$u$|D^wVWW0^S;Mk*)9b=zM=&fi3i*g3!~f%a&DH zO#*2)cx}lI-rHfkYg2?T?}A*f-X^(BT5(B8R9%}B`3^NQp(elXjU14nHetPFku2?` zpZ%TekFn#?>ViFaNyVJ>fCAW&aYE(fpi`B#ebOf?w&ddpJRAZ+)$)$!Q@oketX*5D z?-rsE3K}Ez+FyA+8gX?=zgw)`H9t}`*egVzI(>(svIsygSs1dsw>KQMFEhbzyAiXj zmd~Ev`k_=b&VJk4Sq_<}x9isf0$;@ake|&GgsSlaQDJ^QYAh{(%m$M`O?#^}*B^*g93qE=N~z zLsV#%A977BXlU?=tN{q+WQxvem-fbfG)6bmuSjW&Hx|{Gq_aZX?VUEk+H>hVKTQvB zB?MDAR-3>=^=B*YDfk(}jn+3iTx}rY0DC6j(6{1M{jIOnnp3^nztnpPT>u{sy}0+v zK0Pt(dH{48Yv>*haw3T25+4@?&5+k+p-dv}HLNUh);r{f$BqxGUnT2_rK^0UZ7=!F z;9=kL@N{10rg!)>1Z$G6R^}#YA|y~az4N4F<)Gn4Fe!$tp^bl&8fd^UWUJjLAjN=q@LWYi9l&w_SC;h@>G z48-j9}m3Pu08K-@M%QU&qx=n$CmQkVw9m=79BUC>F7 zwjJ40eMN%|?y;zsrvI`<#M^jFt`9FdM1?D;rw#j^7+l}cmU#bg>*UG4NfHG^u}%Z1 z?9S&AVHHGR(3E)dOEVQjSl$Ya&t!d8(Oaz6pd1;f3Lm)}x_LCJXA#?Z=i^obE%L2mlL^U^ajGx2skc^@5QfAyj7t1$O$Iz@vJeH3!VH~dnwsN+H~44qnSm%YT&%d@(1zXziu8m9+Y9q zA**>tW3E|d5S7Z^KtZneWCFMg&&tKT@IITpn^DIe`G+9@1c_Y_KxHfYAY)R;Tc=%z zp$TUmh&2c;|N z+yJU3vwXiKlr;{J+)D20x6pJluOO3W;35jelOG81yPXCG?YkdfcrTKJo`o?juJa@s zO4Go_SD}%!k`1w_8^!R!Pkfu$0f?kAiRU>h#Ascz3S8d}9L=&zmBD@s3|gD=W_%qI z(4am*HX^Pctgo79*(4+)eEaj8aLfIOP02RhPdjeg&O%}Q>A@s71n86}A(22nz!%fH zx@2~i-?3^YYYgwhik*oQ0>;H4M;xQx;iZ0@9p4_yL^kg2kLXqLL1-6q^JZt{7YpZf zf8R$SP5Q8;$K(OEJfnc3iw zG5$pEAEvh~2f;}o=vOH|BiD)IbS0C(cy#dabOc2sjqF4CGy=d#MTzEbTeFYHw6MNx z*d|4;L0%OEY*~5q4@TT5MAypooNW_U zq_K27zY5ijt34+!5_Z2t>455qJj`RdUBr;ahBmo*bU0&Yz|OTI)#M|@hpbd*xL3bQ zGWZtQVp@M-c|=eX6_ssf<>6^^qLVhq;wMD8do`2a)t4f6W@%FEIKg z*v4uN#yUp5rx}sFMHuS{dbSgfd5;$f<6Xxy+MjJ>7rx8_l=h6+vO`L5q|{BTd+tCy z8=kBSIcF-kR(er|h+aK6W0sV&PG3 zxdv49Y2^M|ZrzxXr+8hyZZ5+_?10!1)6x=Zn;AJ={T1fj&NJ+GTDS26u+Lo`8PL$f zl0Jbaa1c75HAuPaFnYQ$EV)*~(-Uwq?Ccpqc`2vKiy9+W6Bnl|YAmpgQcyxX#+3i) zjg-o+*Q>HFV1ww?`&X*>)n1Dn5!u9qMfpmM#5y+pgk%r>5hCtLownUX^R3P0c}cX<{ygW&5)Oz%Eo5IkMJT z;cY%WXQrQkhw3VCgjUjND*WwJRqyjd3(FxAs#_)q49WBp`C7dY9to0fzO=NVe|1sX zWpr11_|h#e9_j|47~wR3qO~L)dBw?~7Bt0#PYRWEMvE>NYNIJPJMNyA(B%rW3oj&hQmmXZA0SQO3d>*8D?~ z7J0fM%;r^)UdXE+lC#*(HRbE3iSm(Gk_xYUb!TAs!Elbo<9n5!gDMk0pNSx?+dnz| zn>4T0RV(bk4qg=O_8rD&!fUyP%aE@fscdyLF|rUeelIq0a#Ho$HMv7A&J&~}|2q-j zmV9EA>F4V#>MvBJB!yfL%RUBU(7BhzQ%O3bBETII*C|TZ`|C2oZ)Vtoo;(^QjwP>H z|5^5cd48`i-{IUq799JkRL`{|@$w0}>vaib$~kKe28i1!1Fx;9IMOvSAA4F97)~DQ z%LOL~+5IJ+&@NKC4F8$W^?lt#(Xz^MkPM_6-~QovR-W@z&DdGgYUtFUEre8Y>!{WR zyRr5oNjERY5B3P6V)$WMs_lG4X}T`!WbFvX(D}PDyfE+1_S()YN5%t**WG%emm+}~ zAkO8_qlR{ryi;I#+Ktgo_cxV??{;`xFPYkvqqf~&kTg32e%G08&_+`L-_7WDXDSeU zZfjmtm>e2D(WJj7VgDs5P8c!|QR#eGK5E!NvkOTh^%~pK@=_NQg3o(M*XYH%-3on#QNMnCT#j^Y8?ZD?F9lddhtR7x7!AAWbmc_?^c0)AA z$$PZrc}S?2kGS1shon(gG;%qsT~8ck0C6HvqCN>D&A$lZ-LWP918}%{#*__2%bshM zef+Ar-D&yZ9Xas1UM-;z)1M3q_n!<3fEr24HV|N}`%q?wk@iI#Y$bXk9OD!!&<~!* zR`|ywMz}iT4WH5eOY_xhy`s05Y616w3k^n79_Bg4|taix1bbM`BhfDpqSm>vr&4 z4|e7+x5a$|_rTC>zj9uP=1`1E|8LamBT`GbTL8K{5GhBz7yWFebkoIpX)-7Jk!0PA z3VMN3zub>btz;;E#<*0iVHmy#tOz0htpAVa@<_e5&}YL>s2TVyAg}txR#RIR?i7u$ zzUl1=@tQd_be>_S=w$4_GHMJf?6Gr}WGK#*icU*Q`x43_(-nu|aK2NLStiIp+_J3_ z9OlQ98h@+Oc38I3+%&=QYliDgX9_Pmkf6isao^BwA|>imC$uy*X{Q0FysZX7na*F7 zq_Ho2DqHxM(S$+D&Y}i~t6|ymF~|vbHWwzJ#izlkSNS_bWs^BKo^CLw>W5}jnl}v4 zEJIs1wk9cy!^yC0N*$`(M6V(!L>l7BY8tC>qcA{BA1=aPJ)W!Mdifz=YIB`qh3d2| zGT}&Ntu0y7L2*F?nk6&D`2IDJP$d10x+%>;w4@KQMPjdK5O&lzVT5#xxM+#1v?575 zW6g>b^jlBtbA8}CXb~jr?g(}V<;#Q?o&xP*ZsJQ_ZxXhfe`<`H; zIJL(;=_CdX!8(=CawJ<;wla>|a}w%2aJ+dJgNj0fKo=KF4Dw4hxibB1;$( zVhuLR%ObSUDxvHL{=is7ea2e&;8A)ih9+&vs5DEf&q3Ru`CrkV`r{ z_W3}l%+s?mq%6qp7VGChydQ&-7<}R#KB0+^%)@s2;~y8Uc}HX^xQ!_NLT3J)j$pIX z7=SgqAl=E{<@@Zb{G#=dfd7Mi??~uc@z|)RcgI7TCx~Dw}7QJj(wI9?lX1x+% z1|qjrbVPtwFB5Boo!i`$o6T->n$cfG>6iu~FPZmrUGEO+H}44@%^8&zJvCX;N+{Y~ zuL%xphq`3gec-ovtoycz-z8a=PCkMkBJ^9}_1BI=-R-2@97v|f|_koc)<<6Bao8wPQmSx4;R0R4T zpPtw*=tyL9#Sa>lQjjna0Em9SG%3wjk!jiP(ADSz4rb1%z+09{5LB^2bGg&44jZzz zc?L>@kKg3#qhub5u)A&4sWbOkep}}%ee@Z)R4PSBU+XTB1;+|4>+YciLZ-4)L8Y_6a zUqIMNg7T{<5^}zIO_z#>qQ??!2G+z_<5p#*gLm&DBhiFb?$9_J^$TkdR{y`y0CT+> z1nW&zif~H1mOwDPFU`@?dd{#F`R;?4S;M*8i1hK z!NhokzOhUc5lmYI*S#&AG9Tz|-V|P|Ao;$uEa9U%;@ljx?BY?fR0xg_vlxhRkbS2= zxW!#VTE%&_w&)jPi}sl+2?e4 zM&OO%I8K@pop_-c1zhf@&^>1;f*AyxL5r^d;%i?(Zt^S?}$pUq7 zpdGtyBt+=nlETXxrlfc1d))#q>Hik=0GUNmd$ltuL3#aL^}*^F%53dxK8}ab;0SJv zccGJKf^<_jew0`a4Z;>K6;H2)Q<0JhN1vj*wpP?bo*$Py?XHQhCrf}4!uH8IG((!?ej*Z?1AxoR}VU$m9mt~ zC3N#_nmC>1n`9|s!{Dx)txo|`lXSqJy(AN@sv!Y-)SA;r9C58%ISc67#qfD%%iZ1H zR4JwhOPm}T9QEa#Q2r{D`4|{yQ#cjf6JGit!YAW2%Z~*u3yY_uyDHDdpoAvvViXbc z9nN-(JvV1MY;c(lx1p@WIA0Wl!t#qa*>kgP#vOc0wG!VzBg91(d2Le#c&A&$2QP+L zlLxdF`3ODNIHKQIo|-ry@@L8_xVyCE z5&Ydo@GqGAr>)@s_R9EgO#H9Oe|Y^nGX7g*{2Mg?w;1_%-S9t63;#b?^Zy_olZPR_ zzU_!0LmF3{?R#X=oyd6Y}>}VjeAqLiW|Dy{5 c__us(6)hFsmd2yoh7TMyC2hsp7q(&l1DP+cU;qFB literal 22809 zcmd42Ra9J0@GwY1fIx79TY?4;L4yXDL4xbxHfV5%1b2cC!QI^%EJ1_2YtVtgVQ|?= ze*f=$XTOKtmwniWIeokP-Y)5?>guYR@XtyzSQum&NJvOnaNlzRb|AHDn}{y5S`OUJ@!W~6D9#+^R;|zAqKS zaqDbb)W;oto3Ya+SS>92=`P`Etm(<{=@U(yEFFIM#nxVQ=iT)h%>RioT=MVOq*_s| z&&4JbAOBxJRM!8ziCPN${qNh70+P2UPq%t5=>O*OCGwwc_^8GH_heb;qv8Ml@@dom z5epLZ-?DEf4ew_R^8o+Wp`oOC=zY3A(FOa!pK%xdHx{S4NB#KA&!A$!l}SDdFYDpJ za_wf+{qY3n>Ev$psv|{XD&;?{C3Opw{mv3Bq4CQ8w^P*j>uCO`#Qvvx9ou@V7lnKe zXB`Y=57`F-xhW5dUx!?I*wqg9-GmIw*`i` zli-fmLln_W|52W=As_(`dvP;k_%PGderVTulh%2Yv38T;;CG(pzpdrB-T4UXG^xqs zl%k08L#G`%PBRW@*8W87yfBv+xC3@}x{c*5-J=wbE`v)WNLeMbG6@~9708Us6?~WZ| z{G&tbE`+fuQAoJB@VIvTcyW!Jp1?5W6mI5cFwYQvfgkU)n3~RDr|GdpH@GR*@c^?%&;Kb-SB{5$b<*ZFjJp7nI!FLGIfV5dKZ zRF_s%^-4;7zRiVG2ASWl+N6h9qH30X_o^+x7ZO6nL|!@pGJyL#(bHgXlNXgI1MKMW zPqe%L4s`*tTbrwob@NFvg+JXNbb?6bu(C}6UWaTURbu4DaoxU%rr&Ll)H zB#yo2TFqud+PvWV$kf-LJeKo0=}V%Y`PL?RT?D&wrS^s4gMkl3%^2#OpazcnD2Hp4 z$#0fUE@W1})2)MzgRS3afWVps;5{3W`|nNJZ(;gH(u_Fo+I+PZvZ& z?YmfhLRX^lRtt|lp4W8%=l&;7+cukn=Vmlt$6agZo$#^e*zx1haRBq1%VhmH|>@sONS>Rb<8 z^S{PS?G9;RZnb}XXJeguNg_XIOL7C`PTtu5qJaGyvvT^`4lXf7`(o zj87=ioiu7p*p8oqo3zGl6!G3)So%qOyOX2C%z4;S?{(%fsGhkqV}A(5DfsiT$Pp}U z5FAp$$tS(yc|JjMw1_`$?py! z)qK8Fe7+BJ{tr#QOHBiAfm?v>zgk57blp#`7VmOHQvRxudHeokrY`aF+pq1WvP`yz zM$8r|>v(-0SS~jCe#k5Q-)<|pYkonl8K8(_DSE$TZlMqPWH|I^cW3f_79*$O!-RyUMYEvUA`s?Q0O+_H z>--KW6OqY#JLk(*)GIWAe40EXOZ9u~{)T4iV7Ep1j{6d{pLAT7nk7Xr36S@CZa2a0 z*;T(coYQ8f`kC&3DChI(=4;zibiglp8?nXcx54!|n6(;vpIl$ITb{H7rthmTpOH_Dpeg95c<<7rOHV%o z(36|FC68}XW9u=VgF^xCbNW~Fm31@O*qjN0>h54wf)zd%{(BP367CWlSod8m#?&9M zYR0e-=w_U|zgXDJi=lq^&%stX#A@^%pu$lu0W`s0nkv99NU%IST>#waRoYD7G1#*| z$hB{^7pDKCzXItVl*4KoL0&AI z-3q&bq(XPxF`(_Fs&(vJ3j+K-rug(AMWqB@79kCcT*=1~a>d|R`HH}^q$;0n-(&B! z)>U}rPXEr+I?m&|V8^E5<4x9+PGf=6r^cthhfg>4W3|RdL-4${s!WVuoUYCkyy?b? zp`R_KRF9Erd?<<0MxUK$n-t?1zwujQ1c z7>eZ52s7z;bhmUT&D6lqexDxdr&DVGId|W=A`y5A;sEi#n@3pTzXtiQA-?V9@Z%&R zk7kks0qxIQw}_7hXd3EyZ8tvHQQ9uuDgb}oc?b<(`5tPJGfwMYHMeAo-Y8&C7Pnfb zv@&~CDNv;2`sv^MX&WweSEZGi`(`u`lnAW}nN`Z6AEx?j??ly3UuhJ5V4J~InEJ8^ zEIc zwEmZt{+E|coe$A`tG~g@9}vjGMSdHX$v*ILVT&77*F{?t{Mg*a{ZS8m-|v6l&nR*lo|cx@gs|=ijac&t zUTQ zEyOfJEVnuQKkktIZ(qu*nO$f6f4gsrK=J=^KxNVY=W*}d|2I!e`}hc_`zzY{48mnO z$3X)A_0x%w|1~nt{@)wp3c%`VjC)T7I`r>C%_OgYG`V!RS2EcEM0ed}*hMapE<9!F z8~ZZ^m`KiPdX=aYif}ywP{4m96R`Mq66v_%ceMX&gma4Ck&$!w^*B9Zg)f{T5IXyr z|5k$xXL_%oLtY@p*-`&BG0*=Wnyv=dfy0NgyQQ>dXXeH0qk~z8Hb>{F#+B{Sk(Dwu zek{Z~yEKq3+TGd;09}_qRD#;e)m<|RC;Hf7^C&TGiPQaR(S7Cd(l1tQg1&u-?;Rn6 z1{P-xAPw^;$?%s!&OvfkKF;3m;+yjfM_5pofss!$%PXCeFm9?T{>j%M1TVixRTcpb zoMdpH@ws0Wg}B&Jz7FADEmM%EjWu|bWf*NUm|Vj}y!Um;nGKk$#oqS3xgD(Csh2kW zyqgLme2JxgCOa2QDgm4{-~v^Fi+}1GhWzCu;0qxzmnOgUlM`(V^&*_Y#-FcGeMD1` zfJ;YN+j4fUkR5dTm%jDW=4SR5QlX5uVG5r!vgc?nXW`ZrU2SVgVQ*?%XaWkU3JYJ~7gO$oG?w=2GQ-R06C4hrW1y;>(=6biy?5PNuT36u@ zU|*14di{!LW~z?aF@QguG~n2`5>OvmngPYTljEmDM?(5(dP?CUH{5_?aweN4Cmb{cAkLNm zb~&7medj7A@N!?M?(yx3=%=26GOgQB1M8F-q_a1=T^>Lhp!zYc21L>yS+hw)sv^^~ z!BX`S^Cbcbujdwwo~X}V#*qN!vfgGmC2vl+5I#uz&H!Bh0+>5iRNTm1s7|xjI(mgC+8)OY<(EKR~89Qn&Xt@SR5 zSya!(#n=9i(1PzE=Qs)$xeXnlD&C+5C1TzKpGpdhXps*7vlj?Mab{$HbRja;V2`BH z8HR&|bW$q@79#OAa@&NP<=pD%E?{veIvD%Ew%V*QM939!AQV52*wPsnXyPzLqSfZa znKwyK&>r^)H7YMiJT#a;NDzpPX&0y5@jSV_gdjxG2#rGw3@z(9Jv6|A#!^ibJUM~| z*J3-G*(U@6a&Ovd*G8jJkY06gXY3X;Md|1N%BTEdXAk7wY<7rjIu^u&As@(TANlCMDU|JVYd>1 z`c0d5L$0f*l1b>(knFkRi{tU;Z7CjqFDFrmq(J5`dt)0^CU=ue3{@tS3-70|U`$n)acYG$rMApd_ zz#guQ*KZe!y*ZpsWiEA~;g7d^g}d7?-(A(a+m#Qfm%o6lCRSivO^*CyF9$**W%U!^ zz5Am*BZWP1=b6nu0bL0T^FTJ000&u7xih0OD6jWDc&0yk;)rV9j946go>1C^CnXYE z^c5D<-ZcMZ;{1?=>%iU9));m+AQRPF(SsxaFrW^epCU>4F!e&x6`^hqpTI)6?NH8S z+;gy+{m~I*&8Vz-!jel9aULNV&~a>vU?U#dUi^1rA`Z^Ed?7(=%;PJlTIr!;n#yX# zb~khI?df#C-`8#4&Z_kBrntd1mPF36%)XkN`JE&Z4=UwtC^^8fP9iILRV4KT@K&#z zrzn%Jt73Qw#5O8DHVJinvDjZp+{3@t~`c+ z0_zjpY>npn8~6`#RcP zwsCaxH!Q@jyWx!;!IU@D0QtZl_2(1Dr!_YqWW(aiei-@T0`*pJcw-X?+t2rC&YS1a zR={_~3Nk`*M_}#63|6OP=bzWPYIBuKFF!qPdAC=6@UaKwBNGwU))x@y`e5FnH}t$- z_>jJcGOGhdFg70WZac(nf}>oxm23uFvO~)SqmTv*zK>q?->A1Iq>;}V z_H0V+Ehf<-H>cm&0AApmG;3 zMfMLd->fG4wy(-ovySK8`nOP67);q`HV1H-mjH3_ zm6MH>3*4&CeCCAR{A{{CO1j9-4O55vv2Aj>u3ucJ7&!iom>0D;sg?OKz=SBxIQiQZ zgNq5!_-^5#eNs32fe17=T|D<1_!=f}!ktvqiS_w=s!C79`IS3aZiZThD83wXC<^%x zjd+6mca28Mm-Tm)kvAaiqTcdLm9hM2fHUN#)_PIIa(A{xC7-V~+s;Rrc^4d}d!q&Ee65t;lkMwP}KohLhNNTsEeHSW&)Q@rH5>FFmf)3g|`emw58*BwQ1 zS}p-TklC#l@zaxy)IerRb|U@i0~J?!_@G))kt2A(buZTK-0z@K9Y+w?IN1mxz z!-8F;(I96=SuC5mCCm=Yjx~w3px&1?Fb^YrCW!>k+7zs3Dvfp0LS?(`{hi+l* zdEHalGEEgZE0Ta;KT6|C>>dUay9=>F71lgXTH%CxKB(C8DlT24JbHo>;6ttcSH?2A z3I-{+2f3M7Abz;IS?O|H0=bkX*W3he2bI9`uN1GYwrP>@G^Q~NM{BFk5PO*Xn1n6% z(h#YR({9=H%R5X<(JZSK+~sAVB4sPB>`-55>0-=E%zpE!&FRnX-`OrJvq_a6oDfMR zap6w0am7!`vdquuH!$o*mnjqrgil?Jb>gChHo}B^I|Vm=dGpUQu${DZKS+U> z2b9Q)mB4GUF7_Asd3te6E3-vk-v-y+BsnuA$tE16KX~)HtqPC#k?o0z3VEtS)-Y*t zEO`xEf|nefjaV%z8F@t}AvuMM&%)A}IP5IIELp(bpcW!81F-%338IaVn98@;I|s9y za#qK!0$M%ZC3mv0kT@2RW98!WoIXKD2|E@!$3Z|^toLj?CUE1qqwrBP*rHJQ#p0~8 zULAEIPkk~@Fjev)DO}dS8`UZ5e2=Bw42_3u(Bfl@9va{G25#k^)E|~}QXy(^=1ur$ zNH^EH;Vz9|_rx*(ts)Hpw&KcriuMIFOu*)ziQa~ch@ROHjm?s{jje*I;8qxG;i3?g zC8e&x`ic|vte%Y#lyEzYr>2i@<>3p9euY_MqfJf5`(MFd&UJZe>_?U|qm&9B(`KC( z22BU^PRLh$W~TJ5x1{4H`?2d!CuXC^$4PlbRg;tU7cv!Lo>{E!cIWS0oPl207BxW0 zJ`Qx^LC=Er<0Jjs(tQKhm9XE8l5CHInZWu>R5NYHO-7*2bgJD@6%~EH0vE@qS zI6ZuxhZalUB1hm}fW=-l-+HP3z-Fbi9@K^m;>Tr#K|bh+a>w2$Jn!|fs!7P9%nUwj6+jMlz#)N&;uH3tz!U3ulxAt|$}5Ke8?u z%{oWx=rvbVdaXSV>wD`_@0Gis{1O+HbuxNFGCi$X%m$g$fh%~dO0+SYl^4cV_P1x+ zXy+P*j0am7hc7e;o1hk2Y)vCsMl0V*Cs!|i4j4}~+Q$f>@o>Dxkq2(N?t!#FM`4|R z%qxl$Y>`$MQBR0h{)IkS>v$0XlNMW!IEsjFzVQ*s;XN|ZDU!RZZ?_+fahq-L*)=Gm z5EM15WxHOI(%@sV)9W;()|7H?w4A18W55akGK~Rc*S4eTLm1)ixz5gGmEqBx_Wf1p zdv#Thb5kp|B!8-9xpvh)u>R)@`y@un+X_vWgK(aSn#FRD+*HN{{c5Hsgxpm38je|< zf*R?V>y$zjSmq43OpX~PA0ysV=;U-)Qn`t~aHO%lTD2OA-yU+#`)xH?SRb91&Ishg zF!`u=zJxyVs<8oFNIqu-^m=abn{SGi$#SzW$_`?~AmzS0D!ZhAwGWdP$8i4Py#71u zbL^=5io6UtFurRhAGD-?)I`wRxKQ5eh&5ZlVsHtM=jSL;=UNtOuzTp>A^?#cf6^@{ z`~5LJLT{;-BYk`&eALdvjo!?!LhI&YXLZEUx6ozCx58ucDsw>Qjo&7xvtmi(OScUa zJubZNjqGnX9hU-FA9IK;$wLd|YsfqC!?(Y|a;9rrr90e~N;umm3%ethP+F%oGiU|0 z&9~-wAHJ62ZDqv4Xe`aXI*}ahTgI}y>N1<`tBUDtIjUlwO>(EaKKh>tzw2Jm{7%F6;M%`uXuE1eGXOH$qsDZUrNMGk5%u5Or<(W zOvNy(X&?k6fVdbi_;!y`%z2rj$*%07u#&Hhn6t(zd#J+q#ev#7SWGPWYl0W74On-1 zp!*wh@cj}_{U|J^hFzMUNwwQmOP8ktXR+9wE;SrNcTIG5d&|jENnm;_9g#B9;-`m~ zCx^jir8_e!_ovu*H-xifInVdt<7WvM^}~1VPk)#?8~E^(r=LB1uue+*2))&wqOMmSslweZPqct}5#+QC25WO{aix)HS`N@Og|)=Ev4LKFUE0 z!(ide=$ARhb0s`)xGM!U`;#c_Eara+YC(5Hi zuW?Z8ww<;=Yccd#+$%xSV`eM4SuFCubhGs{C2ikXi>3gf)$&tbV|6Ee7SbZ=s`Tjr z0%&eo^tsgQD@Pzx9^%WGT6=!J`h*SDH#;b5ZiAYH;x;S|sc?l#K#?rpOdh65p$l_@ z8fNm|g{%q}h7`FA_VBXm*o@aQPkE)e_b#ecui9L+b>i4Q0ojV4W)tb&82Qk6E#&=f z@Px>y&ee05@Jd3mu5|8}e z{r5riY=W#Apt*e~mtSpll@(nW6<&)2-r|Si=opqp-Ht1Z*PD2CA1KQej_0pUf-2wn zpDL+ae%fskbv*kP-H{>(s*G0E6n1N9=Op~l^XS6_`_#Tu!Y7E+o>@9oS@7k98uF!P z<25(Tk$_bBvWd!NWSHE}5F;Si4cleZ4NK}H6rv(z4UDMIoKZkCD+jpaTL8Cnq6QT^ zQOb?(41yKjm5e=+4`4%qVZUnjMLHrB8_Vur!a^|5ZhXa290yy|s+nS!ew{)l|BONq z*W{R}jAy}4>`+FVYSrKaX1>%EN|n1XW?jo$rZ8w|MfH=jqu68-?e)s=SHW)yU5)7B zvOg-1X`NJ3$}7S>tS3vg*hQfq)-rJSEOswpmu_58Y{nz`uRYxEgV3hHAv?TF5w z#1en}aeC#Svc|Af=G{xEi=(Q0K!&r7=nUqKM+yy0w2$=WL^F8ISbO&k*W1l$^qMA8 zhHlITuN|B3J`F!Am1s4~7PjA4ndul@nz59L#=>oXjA03($Qg?u@i;DiyrP5kIo}4Y zPV+4>6vuc)Dc=Q({SAHnRQdG3nx8t$tIT5`Y~b9^nnswx?>()m zXGjL3c{}X0rs4kv!TFSAPMrV>)rslZhG@PVDNyWrp9Y&F?B55YEZ>r_F>lXS{O~t7GJv z?H_F?aov^mK7Y8rXG?9?1$f5BJ2eLtNxX#;OYOn*9!##;Qh6Lq}#-26f)CL6a>(?aWun4+L z4xvPnFTtaS-iH5+u9**6?P_xIsY_7JhRJykj8bAPHO(n3!suxY5!`Ygr}yrfEhq`N z%sF}1sQ=VF(@9F!WYQ8l7%T+F50XG`r5V?9BgUv#^hToNXaG+_Dw@BS>Zz@14zX7N zh6BqPk6il;rOupX$}UOQ5PQ{^4Y&-d0MHsAl;RCm@~6)P<5ZK$EQJqjyMDLJDjeUn zym<2HA7o8pL77R&6L<)`>BQ>vtSmU>clk1azW2Lf)^kQwT9xlJxi1=_-#k_UU#~67 zxj^ysM6T~IzLVcuSWXDEl`~t|Ck#hhJLj~`jKvJaW;ljj%?4W@_g;w6Dgo@T-wcHi z^{JwF%@7cfiS{b_+2#s`JCf$xGAWdxt8I1jpm~f zyT*yU9W_I~aYF;BG$X^@m;hHu0Nz7cCxp0En`6bW6%$@2;;mbS&&)Ps#+>N2&`dsE z-`*WEY2d#>Z;bRcgxHa~UwrC1H4ukR8eD^^x2u;NrkS*Qokc!HvfX;|_DBv-<|0Dk zOQA=>!Y|;dkS&|Oo78DA0Uyu8C&?lc3oFf3XASUt=8t+N@aPgzss?;v%8O98TZz|Z zsTSx)$&Rd6^oOyzc4-}9R0{iqd=9~b)~mGtfm6Vbh392;a7{|m zFm`PN^70;Uq*JTP97e@PI)R*-xD4>KT1i4nHfNb_g~w}ytw!u=K8HCw%4oNoq=Zp# zKy@HJ6NEQWwr^<6;SG5a#-IUAh~(AzN^-T@$3mT{zdI)mU!tka+t2;LYEhKOoKn{T z{O@YW-DhbCK872)h@~gDL*1U@o?{~RM=6$Qx zQ{Vt0FKeY#o4iDgSfHIdgH6vvjpTTyw?EDH?Yx7mv^6 z%&=dn_Z;bG zBr5PsXrf6dVxOda3*SIFTF^$R7!efzqBu@3?vZr<1IT&ePxHhLoLp(#E44;-cl(=4 z6a&Az6?{GQ3boAL1SbGJ(&LFi1^BsyWbnHyD&k1Y`^vtsb$Q*{{8nHi5k~(vAIfuTDz@E3-k%;YVEO>2fNGx4!Y#8`V&wxa&8+;GIH2AznY>VGEQUXbMzVZ&=0jA;@+=FAOCzcL4OOd zUQlZf$+tFLhxjMSkSe^b`U^wt;%z<_S6_+K$Mt)1)?goTNC}~i8GxgnTRE4}h-&64>toXQ*#n(g z?;QRB3Gv>1NW8}kll%Vd<;1`({?ib9seJyqvr9O?+8&DYQIg2MwZnA;w%mM2HqI{y``>N*qHhqw?m05CD}QtZ zAB{1>ADnoHJ;O;;ooj(Uk5a?{D9u4+|JQ58z-?~zodzEyk1_w(!2jKK`u|U_8N%Mu z?(ORDP2hg{1(DL9<7;mB=lLDI{L-;*^YT$bkfOOgLBOsct@Vz;7rc6;Z2Ko1k@28H zkom*e7&zIlK7oR$9?+_Fu;<&n99dzR!FN=yNpE4t%e$&#z!y)wJz{(T$9KnHv;nsS z$247;74rz7%;HcW7HHU*oC8R{S=RdSiMC^Cyc%eZiT+FYNVd6>Lmaa}aV39zKVJ83 z825Xt(ti|*3GToTE(>!o&nrXc46r;3YGyKiSVgWjlR>*cnY|r)1kvJtB-Vt@YLSx3PRbfO<>dbC{gV*i#ttVIwt5So6 zdim3&Ku&z;l9}-W#mo^}_fZuty8E99iO?MYb#&ZlYlK|M%aT!Wkb-ocIX9y@)UANq zHj2sgU!pJp2E`8z_6t|WH=I)$wC@c1RBrV?re?m^t!7pjzHVajvQ?KURLf2%GFFv{ zg@J>mef?iZKA#zvK(19|qlEL2h_=+W+#{+xv3> zX{+IV+KLx^#Qn>e>dFl;0n#`DPUvE0-_)D02e~qnQh)HHc-~)RBEq-YroQ~$wHUh1 zlVpW`_18O>A}zR79201t_hmls$Z~9~tm6-PG_rbAUG*0*%bl$->{-)O?vtDC12+*c z)2jX^lrNoFfg8uEiOtM2YIY@Dk*?eOGUd}EO8DZgOE#bGI<%zu4CITVEhhQ_y$!{m zB9K(&wHqxz-~Hh&M$?p$(HqEKcVA{wsHLrwhOcl}@LaO8l>auuT;#v3`7e7q)NDW# zCLZQ058r1}1}$Edq(HtmTZdRS<*I+N%6H|h&&s8~c#Kw8)qAVvqUtu&p21a^iKq5q zvFLDb)M$Ls>;rKZgqy2zB?4cJg?Fk5*wY6?{B7ZDyS^?sYQsy$^TRaluaREPCdRky zGnKH{`SPRguK7i)(tVX~#cx`s^n}_a!gY!y3X`<>oby$t2D`kz$YIEIfTCOU>L%SV zZmp43Xi`L764;}SRZ-A3SDq>yxna{&(vA9LqF&r!NjVzCv25Rp56PW2Z{;@IH;qu#?w-BjI7E zU=0q2dOzDr1D;L&f1>s0BNV^?J)fi`k5(DJH!6>dX`Je&%MwUZAu7P%=wL92fVm8x zWSU0~w@K#hYAv(R4P@HhrvT&DNRhN|)I@ozVc6<7HB;_C>%j}2ruU}um|qi&H|eQw!ZCL(M0hrOx!ZZVYX zi^m2J8a{N)IO{z!uPeFXI0x~rIyxhYb=H6DYlEYxUU6O+qd%P#dvDX_ja6Y&y`L$h z_R@H&NF$U>?^(&v=FpJ~5^*mLx>&Z8y6^%&>n2OT`)*_9v!8;X0=qBxcryrU|EI=J zkg+X*zj=%0BLkXs7JC^NXAsS+I3F^&Ym%SP+NP#8`Lg1edNRm+jy74jE2B%J+2v1$8;2X-2$V( zY%+m_F*;Vy;d#mQneQK=Uun9%N|2UHAxgcECO>YHf7#v@x4(~@(2N_n8o*pp!l&d- z%Dca^m|u)9wdkhS3SLxgeTDEZd@D-<6pB#dR2v2cfaLE)TVHj}E@!bSx}cXFoc^8< zZ-Z{{GKjU8H3F&yF%O9fSu|ez#0chyH|tO6ypy7278j^6cUN;0?kDrPnweg&G;VS! zwC%C{fslCqSioMqi#eS>S2Z<%S#cIe4bVUClX&)_tZlul2Q81PI*}B<9+Sx;&G84b zG}ab7m+lu8@Y3HQTXM^@nYMZq)~&V&LzU6L^}_OVQ8f%sSCbfJtGMM#mDNMAP;)H; z(X8d}ha{|Ct4AxWubad-TBjdKAtdUjM#}{pAKg3pAfs;lMvhS*o)EeND1JTd@Cck+x&->k+9|0R6vXmIb7fF0J&Y_e6(yU6a)z9 z*9;?NizDsdxXwYq09bCAgF&P3QFeyN6FJ}7Z*r_f`Kox^)#K9dHMxI%((snEb9!1V z6{*z_b;Gp|5N*S~gE?&)9#($dV`FX^AJfu8yMQ4FsE0DI*@`=3%+|u^ycS4mr*I^` zbdh0~cbGcbb6BT2S#5?5u!3J ze#HD!Ai*SGy}UKPeNdhY_t5+xrSYKkYGlaG+{M^QeCD2e@sG~&E*UaHHbcvx@%R*M zZ_1j#xLi5bLrUW2g)BpKL4e1mTGjr%hMl+i@`vc-19e0TTXqlppsA#T+fP7!?)unp zw8?6!+Kn{u;UKij>?hS5bT!M?TYkBDA>_l6mW2a^g5gJ-i(|+9N55+4w7lLP zR>U==B6hjrx}PCaK;&p=%oS5;lJFjdKEI*Pc${}|xoX>d?Q zaGAZo;CNHk%vsDZ&>Z{st?p6#hfd-3eLbH>80t-~STiWcv98wh+X>UR#a1)Qz1W`^ zrUk~Y(T&vu3GKQ2SO~aO{eAW<_9NQtj{FM0`~Wn|0<>*{wqm- zMc|~(uktI+?>-It8@i2MC{YtG`s#O|3*iVvEoup< z2kvIG+y|;lI~yv0I9e85HkIb8Gi{tcgY;?e_i%c#nzb+RdkoI%s5VoID5D}#hC&Q# zPjhC_0J~#Bw@q3XT8q|ZC5FPviz0Obh2fHE;>W)J`i?mX%1^p3yL5bFD+LCCGTjPTPkStjd0zTl^@cT>|;j%W4l@ zJA2fcUT{Od4DA^?)!cD1kl$3wWhzi1)e9Bf@tb2&zsb7oy*0AwGPx;MP`;49HT9@T zR&DfNOI|e~(1R;tgtx9_&lTWwZ>W(t3x|pk_EG8$wFYj-W~s3ma}pORr^^NBW9hYecK47kA(RA>u32KOx9=_ zMdqZ1MAEkD@ZVVWi&f6B$n+e zgCsP#1Wcc=*ripLTIgQ~5dDl?mx9Jyqs+RhmL|ovmM13^bgvxMNKHPHvr=u&61F?2 zIbgwSS>?}wQ&%=DAm*rlv2h7R<*9%}1OIM0fFn9<#i_lgQo!|`Cg#GA8%@dGoqbav z;|^6*F!O~ML2zjy<+G(Q%b;ON*QTGTGF4}Z(&4xY#N>p! zmSe9Ewg+M+B=DYzbiRKl?o@^2R@pP4I4t^UCDcw387YcZ_zFTu$7n2}nH;|~%b5oW za`1UHk0wbWa$~1zcOY;eWV0TkD?j6u^2WHVK#vwC_vmp*6LV2e8qfb3rzWaAJtS?O;vcXr;d8BLID{+1TGDnIcn zS-;82p)%Wkf?W7a&Ut+Nbm_VPWahm1-6B!e>W6CfyD_1yhNF2xIoz!#iKg)It5%>D zdL6xVQz%0tAn7)&;vJYR#PCK?LW2+8^>cSK6hDn$Sj>fCi#sN17aO#+?9h-PO?|~4 z;&sX04}BgM=vY?RqNMX1jX!fdgsSw?Dt`~UC*R10^pih@T_+^i7zOy;e0x0By4f^$a z9kyN!rdXcRIh9rCi0OBFf?LYE4sLCl-)IhJDlIatrt2DhXW;c8S;!6*Y2k>fY?w!K zr30OKK?aTJx%SnJk5rDd4G-d=dodfH{YH^RLejS?v!sd2p9|dT9f!excH2J?Bf@d; z?3^lf<-&t$YDY0I5^lBfMvjG1B&5xcd+|IFg^<}>4R(KU*@Q@l&9_RtssrJlgis+}h*JeQRWHXOW{N#XS(8MZr%QBrG2Rcstm;KT;8W9OwlB(@U3c3P#h{DT3{k+Y%m7Kh-5hR?hDpua&ZAsf!Fnyulh1sr~{4K+66;z2hQ~qP}^@eR7M07|O ziY62^|0Qe5uXeDbpL@EjC0p1!X1fn&*;RHv7W2UkIn07~HjAa@Mw#Gt}5n)gEl9 zEIY)V^~vTOXXlcIlS43dM8qzGMw(C8hbE-8kiL7wK=NZLLVCX)Id|$NE}yBeeaiGL z8_1q@;n#gn4Xv&>Lj~sExxEFRcPY)>+~oFjJXKiO%5g>J$q_mf&8qo+WNy)1jDM=I zHRH-9;M{j8V5R8*wZ!ITuq`p{QV95S4jl=E&AFn`C%4Y>maD5^m{|&?uBX|M@eT=d z36k3aV$C*JkYlS(n)_uftBai17`y}uH>_go)A?+rCozZAHxzuZg~(hAGJ)H#p8u@d zK`{KbERKDkyWy8Mn|zGL2ot7eLZ(W=%@la9VJ}mWxujhii>pB+ z6Owei0>#q!?iU7GqhGgC`n~Nqs|683=FK0^Ju*BqA!ZGU+~q{{G7U(csAY2f)u$%> zs{YlI1-**ovjw`MsS%K#S`z~e|M&Wgo3jNGWJAfyeGIuSG_6a29a1UC|KX5|0808c zet5BEn2SeN=zKRVOcGZkYp@y5%&$#@vFH~P*)Muj9K0i?+~ zo)E7xgm#G=N`=nkZjo>lbQ$r0zW3TpyL_ae3`~@6-SB8IvzbyaHD`~Z1LkVv8o~>0 zHJWF6G3kQ4OX zJZ2zSJ#AXeEOS#+`G8r7FMc^}rIjncU|3$rK{_u*Vd$n`%k#jL4l6@<0zkOx&EzU9 z*O?pGYM|*`c5@*&w1!zv&G+!ZxRIx6irUz@V3fivcy$vSNfJ9BaG(Z>^~$Mj0$q43 zbkboFB^W1?u;SJ%8u6U6&Xz{=$Rz*bUOr+;N$it?kZGm9;UU+WNTi3GX{A$sZ_@^o zF&`4;t^7=9x(wlZ^JD7fHJa&zYFUNh{tGjzvw`Zqwz`G#;hUcfFLH2MQ5EO2bW|CK z-Mg@p)zv&F2`UL%iiuwqRSG7sZR~^aY@|bB|5rQL8P&wvtvSbnqJWBkAcTVo(v@zD z9FZoWN(n8YLjV&%2)(FClXd_hXy}p90|+D}B#KB8kWK>9L_!Y%X`&R*9rdhp@47$k zpZoXwHEU+pytC(hXYaN5exK)w#Wx3JXM|NYDVd92rO28!zfG=MKiG~~ztbP(@c7j_ z>Z+1k_h5JIUpEnxb#9jD<2Xl>t55J&IPGfsg|%XdwYM^_S#2HQXioL-uwE#feXC4C%C$Pc^y}_J@84LRC4F#b%M`-sy{yo(uKGl?c|l7`bmOE_LBS7GMVt z)nTS%w-58>1SjGNLhyS5sruMRS<=TglgujV?_6^&Z{7W zVcY%hyGjBxeu^Ddy$`~9a}1vxdoiH$v1(ndZhz)M_LIXyBhL;V0cXiJ3iw_c+$a&_ z4(8*f4kYLycRN9W84tBAvFD?Br+riUmGS#qY2q8voKxt-{|8m_Q$IQ$%p*(!?~L9K z>7+%XjKm7wM<-nDe6IWXU`IT+a`6q1xyiPyt)VJ4f6>>E3peb^a=If(<9R zWBezB-j0D%*3P&tELsGm@d0)B6Wu`xVT@DO3a%k$c#l}sVpuvqU66@45%6!;vbHin z{a#ArH{*i@ghJ^ZKv>?;Ab;|IL3^_d}D5EsYi`@ze* zSKij!LpQIHxa>kSTD0*u-%F>-+Cz8$2>70IrGz)5#-n{$=*OUqE4nI{<4P!sm{g8( z)&YzK_e5Va@l9ex+_2N-C#o0Kfj+G~PXXk@_RWt6E?d4S;q`N4C=>>=jp7NaQ6r_` zKoraa_-^4<2Q5F8aYz|1PiX713xdS%i&ehrrs|7v4Z9$iJq{Fh#D+wLR4JU^Uf%=U zyZdalW4oE_9&(h_23wftW&PvJ!Zz6qoi`Z1i1@n`-XVAAudh}q6{@-?t0%n^G+w!` z$2;a+X2NXE^-m3D9QEWJF(72C%@)(n7Ehn39W=|kSc3E_F+8ij?kr)6H~x)|Q$V^l z!$@i)F#&Nv;mm`fz)#^~r>5%;-5Kj#j-T#^CC3qmW?%C{?rKD?9O0$k3H@p%N-DGC{$;FSC;>FdpCQ?_YKlE6yzgUYqUTPDlEF^=M8<1ECEg;Ud-~=BdhuEz_NUW-eQG z4y&Kg{93}y9P0{qM=ibnQ|wXqc!c=D%F)a(;UPZP(7?2cRV9~{-VA`t5Oh`kA zjd3166Q-f&tA(t@1AD9J^S*nOhXf2*xFCe=Ayzh?#maUnErXnIgDF#w?6fO{w@)jZ*MlX}tn3TOXYE=ufK{4V87gf`eTnjyBK;xd&gMg#KqYa^2lZDq+UN6rrH#dHNKGp9zB=g$G%K1LI#={ zd1a#kpLA#gEFlc~K3iFF91(`6%OZjn*4Xkr+HD%Q2A*H4bbg`S$-;+9gE1%g$?I{9 zYz;Uq{%Hbdt*_10CG&k^=7i6#k!?!zNE$ERSRmFh%I(@HAswbFyZ?k4(J9j#HnuX7 zr|itZ(dTFSGkkyM_?JNkLepD? zXz1~eUuL6CfUs1)hM;I=XrBhr6sql$^A-1oml0&gAZ0O){#&`sVl4epc*-fMAte@D-)Qu5J-xT_Q<_IkWW5IKF%~fvx}0M!GoB?x7vysBTa;uK#L56m(Hdravup9J)=)Y3|T) zkGNJ?IGyS17ueSO5^(yaqavCZtM^s8!i!tl1p{)a`TB?13V2CWB0=XV`#JhJ^Z;tj;FH`?IW5NSBr4xg`^^o7HSp;DH;7KyMvud+YBy z!eeh*T|QJpQcz6|eFsRX3*`;1{i~RI`{v;3JKJNT^y4;9ECBhKxtEb=KUlA; zHA+HD?$=?|3`ph{#+E`$ikM%fI4_*)K;DjgbqgmSQln_fm84KZ7(^hY z0)pDi$oZ1-JYQdCPxh*3y-JOJVh&A|ue=T1dfWnOJak49`SV9gc@ym)e<;j25PZHR z&^UPu=KL1&a4%G<`D=I{Bg`Y*4;x9-8e^m(IPp>5?W z8iW5n){4@K5{J1_FGX*WPiEynqrWd`B!c1HL3(4V^O4EKo5zMhRYO7|Z@fYdam3D~ zQ7-Ha#8w6npAWHOE8={ATZD>n3w@jKrDzCM6nUJayCPg1Ip@syp7~2BYRqQFN&F^y zcZsrMU0K?de0@zR6E)?g|CeGtQ@+UZO0^fRbVI%zZ!iuIn+ZWjMn)FH>N2-dbgnlp zO+T2LXew{&=}7yM;P-YfCl|`&=bR6TVNSTM&>g7VrdElFtLbAE^ZH=hY-m?+^XU5X zBqHm4kZKjjREeDt=CQ@4OI+Oq2d~b{o%(KObajU4M=itbS34#A1s)CG(_laH6Tz2b zjriG7b8U6_S+jeS#@VuR3&UF0>9Vq#uGcDtaP){rU(B5lm6&YTUGQXxf~^?qzyzku zxpD9Q#9_*}XL-=*wSN2Lz>XNGg>UF-;+UFWJ?Oc!2N_Eu7SoSXKW(J3iRI^m3q z?F=vrFYjJbd#kGH*BGrc#vs$r6^V}sU2|VcRXY_)K^qLUj`vZ-tnV;0#qDgchaqfn|QmVwl5d;hKUsJUj zeEi@rXmLp3OV5(N;c*=mU6HNV-}2It@~ii-nwD zZ>0-c{RJYnn-{btfEUkgSMUw-RAcQt9QN8iJ-ux-@)Y0?vC z_hL6Aq?W$-g-XZKSut29IB7J9#oUqdM+zzj3-_$QJH(cmue^B-%syi5I35soP^?#$ z;|iDVkVJPlHtyMAhQ~P`x43$j3R|N8o5tRy{D_4&I{x0qnl|5(z-Ef;up48-0}oU{ zM+GfdD9PK}d9DfQ5rliKa3O_SljeSyvl@@3PFXF(v4X~^^r%#d`NZhFdGFWpGu>Ja zA)D&$p8;tPSb+qsb{#&cTN`+CK$RT*4t29}0XM|wKeHJ)Ax zQ>i-k%{AH(Fvsn3@Df5KWYF8JULLls;vRu|&y&O|0X5A9Z4ZXh69auutBvKdUEG6N zRGTmXPexv!YBA_d`x+NVa?2b44nnK;uFZ%~+*NyHVN}I>&!&Uc+gwsofQsP-9lgD^ zfGu06M4*dZlcQG-FA1qK%*&r1T6pQitee^&nETMW@<`8ORB+Wf7?s#lKHL9MWYs7J zPKOz$G*_^lBmr<1<%=uum;?>`muS>+Nt5fMIDYl^ z#@J_2Cb=xswr)$cH|Uki>bzd)I!X;0V@BEFyEc`09$_~OtUd^kE15XweoU|S%CTCy zF4vMbX_o)sei~-wGUtwVgxCOKU5`B!lkK`yXaC!nc#6vUTj;BU(VnA)#21jkrcJehK9X|R zhPL;(FO%xVeYoz0@9k2&wmU9s2xN!0j%5++&1h`=ezvLhu&o!FKnP{4JYB894bT?u zslaFba3+RRSzCZ0=W>r#C_TU;koeP08JYEUR)pB94QQb4ZVeIeQ`@clZYut#qZUJ& zpP$StX9+%iP_cWr_|?uyDCUXnHi9-PW4P>ioJ7t2Q|0vbX%_ya-Blz`%9zV@!;QaA z826Cit1PjApv{^HU*iTnHIl%s6@W78(6gU9Vmh0iT=}i$rCPw?tQ_xq&q2?nvJak+ zZN4_!PrXSkpQ&Y~^Bq>nfvuTP6yb3QC7=%We&z`{lbLIz<2+PGF&n^3E+cK=HMQ)S zBk(R@^%MZO2+?POOU_?5vGOs0aCi+JkAq{=VfA4<&4U5(dtGIDh6%F))xt;A!`0GmsZQP?FzzGkHR?|K4PthjDJk84&c1MoIo(LL zNqI{+QZ(9PY|8*u1BRw=$SD0Sch3f1&8F`hfw%v(f(~FK0ZE~#pUOr68Wz~UeDI&c zf2QC+f5HF95&BqZfmEibZ@F7m$}i0@Kt+spP{@tzAIULg*twQ{Q=_@0l(3oezpfp5 zI}a&5t|)$rq*JK%E0b=`0&feq>YbYl__cEnNR!2%km#^`dgO-v&uYiH0P?tA>g2By zP?14#NmmDyN5Av@EDZ(!(L(!FK$*db?MwYcg()-xwOYHzcVGV>DZAZc&c(Fe$nSr1 WmWEkBQD6NP4en?gX%RFX9{m@pIr={U