diff --git a/.github/Intro.md b/.github/Intro.md index a86f2266..6cea74a0 100644 --- a/.github/Intro.md +++ b/.github/Intro.md @@ -92,10 +92,10 @@ Материал этой книги будет пестрить множеством ссылок, которые в электронной версии этой книги, разумеется, будут кликабельными. Однако, если вы имеете удовольствие читать эту книгу в "аналоговом" формате, для вашего удобства все ссылки будут представлены в виде сносок под соответствующей страницей в текстовом формате. Текстовый формат вместо QR-кодов выбран чтобы иметь возможность вбить ссылку вручную на компьютере (все ссылки будут представлены в формате Unicode, так что не беспокойтесь, что вам придется вводить что-то наподобие "https://ru.wikipedia.org/wiki/%D0%A2%D1%80%D0%B8%D0%B3%D0%B3%D0%B5%D1%80"). Кроме того, "умные" камеры современных смартфонов отлично справляются с распознаванием текстовых ссылок, поэтому авторы надеются, что и с этой стороны отсутствие QR-кодов не произведет неудобств. -Большая часть информации, касающаяся архитектуры RISC-V взята напрямую из спецификации. Поскольку работа над спецификацией все ещё идёт (хотя базовый набор инструкций rv32i уже заморожен и не изменится), чтобы ссылки на конкретные страницы спецификации имели смысл, они будут даваться на следующие версии двух документов: +Большая часть информации, касающаяся архитектуры RISC-V взята напрямую из спецификации. Поскольку работа над спецификацией всё ещё идёт (хотя базовый набор инструкций rv32i уже заморожен и не изменится), чтобы ссылки на конкретные страницы спецификации имели смысл, они будут даваться на следующие версии двух документов: -- "The RISC-V Instruction Set Manual Volume I: Unprivileged ISA" — [версия документа `20191213`](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf); -- "The RISC-V Instruction Set Manual Volume II: Privileged Architecture" — [версия документа `20211203`](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf). +- "The RISC-V Instruction Set Manual Volume I: Unprivileged ISA" — [версия документа `20240411`](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf); +- "The RISC-V Instruction Set Manual Volume II: Privileged Architecture" — [версия документа `20240411`](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.pdf). ## История курса и разработчики diff --git a/.github/SUMMARY.md b/.github/SUMMARY.md index c9a9ef51..eba5abed 100644 --- a/.github/SUMMARY.md +++ b/.github/SUMMARY.md @@ -50,13 +50,14 @@ # Основы Vivado -- [Создание проекта в Vivado](Vivado%20Basics/Vivado%20trainer.md) -- [Взаимодействие с окном исходников проекта Vivado](Vivado%20Basics/How%20to%20use%20Source%20Window.md) -- [Как сгенерировать логическую схему](Vivado%20Basics/How%20to%20open%20a%20schematic.md) -- [Ошибки элаборации](Vivado%20Basics/Elaboration%20failed.md) -- [Запуск симуляции](Vivado%20Basics/Run%20Simulation.md) -- [Руководство по поиску ошибок](Vivado%20Basics/Debug%20manual.md) -- [Руководство по прошивке ПЛИС](Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md) +1. [Создание проекта в Vivado](Vivado%20Basics/01.%20New%20project.md) +2. [Навигатор по маршруту проектирования](Vivado%20Basics/02.%20Flow%20Navigator.md) +3. [Менеджер проекта](Vivado%20Basics/03.%20Project%20manager.md) +4. [Симуляция](Vivado%20Basics/04.%20Simulation.md) +5. [Руководство по поиску функциональных ошибок](Vivado%20Basics/05.%20Bug%20hunting.md) +6. [Анализ RTL](Vivado%20Basics/06.%20RTL%20Analysis.md) +7. [Руководство по прошивке ПЛИС](Vivado%20Basics/07.%20Program%20and%20debug.md) +8. [Руководство по работе с ошибками обработки кода](Vivado%20Basics/08.%20Code%20processing%20errors.md) # Дополнительные материалы diff --git a/.github/index.md b/.github/index.md index 31d857a5..860b8d57 100644 --- a/.github/index.md +++ b/.github/index.md @@ -2,7 +2,7 @@ ## Полезное -- [Создание базового проекта с прошивкой ПЛИС в Vivado](Vivado%20Basics/Vivado%20trainer.md) +- [Создание базового проекта с прошивкой ПЛИС в Vivado](Vivado%20Basics/01.%20New%20project.md) - [Базовые конструкции Verilog](Basic%20Verilog%20structures/) - [Список типичных ошибок в Vivado и SystemVerilog](Other/FAQ.md) - [Тестовое окружение](Basic%20Verilog%20structures/Testbench.md) diff --git a/.pic/Basic Verilog structures/common mistakes/figure_01.png b/.pic/Basic Verilog structures/common mistakes/figure_01.png new file mode 100644 index 00000000..7ca35f49 Binary files /dev/null and b/.pic/Basic Verilog structures/common mistakes/figure_01.png differ diff --git a/.pic/Introduction/How FPGA works/fig_15.drawio.svg b/.pic/Introduction/How FPGA works/fig_15.drawio.svg deleted file mode 100644 index 97e07ac6..00000000 --- a/.pic/Introduction/How FPGA works/fig_15.drawio.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - -
D-триггер
Ведомая D-защелка
RS-триггер
Ведущая D-защелка
RS-триггер
D
Q
Clk
E
E
S
R
S
R
\ No newline at end of file diff --git a/.pic/Introduction/Sequential logic/fig_01.drawio.svg b/.pic/Introduction/Sequential logic/fig_01.drawio.svg new file mode 100644 index 00000000..9e51a8dc --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_01.drawio.svg @@ -0,0 +1,4 @@ + + + +
in1
in2
out
in1
out
(а)
(б)
\ No newline at end of file diff --git a/.pic/Introduction/Sequential logic/fig_02.drawio.svg b/.pic/Introduction/Sequential logic/fig_02.drawio.svg new file mode 100644 index 00000000..8172c5f1 --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_02.drawio.svg @@ -0,0 +1,4 @@ + + + +
Q
Q
Text is not SVG - cannot display
\ No newline at end of file diff --git a/.pic/Introduction/Sequential logic/fig_03.drawio.svg b/.pic/Introduction/Sequential logic/fig_03.drawio.svg new file mode 100644 index 00000000..83bced15 --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_03.drawio.svg @@ -0,0 +1,4 @@ + + + +
RS-триггер
RS-триггер
Q
Q
S
S
R
R
ВходыВыходы
   S     R     Qn  Qn+1
n+1
00001
00110
01x01
10x10
11x00
ВходыВыходы   S     R     Qn  Qn+1...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/.pic/Introduction/Sequential logic/fig_04.drawio.svg b/.pic/Introduction/Sequential logic/fig_04.drawio.svg new file mode 100644 index 00000000..53c45a04 --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_04.drawio.svg @@ -0,0 +1,4 @@ + + + +
D-защелка
D-защелка
RS-триггер
RS-триггер
D
D
Q
Q
E
E
S
S
R
R
Входы
 D-защелки 
Входы
RS-триггера
   Выход   
EDRSQn+1
0000Qn
0100Qn
10100
11001
Входы D-защелки Входы...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/.pic/Introduction/Sequential logic/fig_05.drawio.svg b/.pic/Introduction/Sequential logic/fig_05.drawio.svg new file mode 100644 index 00000000..edb544ec --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_05.drawio.svg @@ -0,0 +1,4 @@ + + + +
D-триггер
D-триггер
Ведомая D-защелка
Ведомая D-защелка
RS-триггер
RS-триггер
Ведущая D-защелка
Ведущая D-защелка
RS-триггер
RS-триггер
D
D
Q
Q
Clk
Clk
E
E
E
E
S
S
R
R
S
S
R
R
ВходыВыходы
  clk    D     Qn  Qn+1
n+1
0x001
0x110
1x001
1x110
0x01

1x10
ВходыВыходы  clk    D     Qn  Qn+1...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/.pic/Introduction/How FPGA works/fig_13.drawio.svg b/.pic/Introduction/Sequential logic/fig_06.drawio.svg similarity index 100% rename from .pic/Introduction/How FPGA works/fig_13.drawio.svg rename to .pic/Introduction/Sequential logic/fig_06.drawio.svg diff --git a/.pic/Introduction/Sequential logic/fig_07.svg b/.pic/Introduction/Sequential logic/fig_07.svg new file mode 100644 index 00000000..a3448cbb --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_07.svg @@ -0,0 +1,1187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.pic/Introduction/Sequential logic/fig_08.svg b/.pic/Introduction/Sequential logic/fig_08.svg new file mode 100644 index 00000000..38168b94 --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_08.svg @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.pic/Introduction/Sequential logic/fig_09.drawio.svg b/.pic/Introduction/Sequential logic/fig_09.drawio.svg new file mode 100644 index 00000000..f572f0ba --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_09.drawio.svg @@ -0,0 +1,4 @@ + + + +
Tsetup
Tsetup
Thold
Thold
Text is not SVG - cannot display
\ No newline at end of file diff --git a/.pic/Introduction/Sequential logic/fig_10.drawio.svg b/.pic/Introduction/Sequential logic/fig_10.drawio.svg new file mode 100644 index 00000000..2aaace0e --- /dev/null +++ b/.pic/Introduction/Sequential logic/fig_10.drawio.svg @@ -0,0 +1,4 @@ + + + +
Q
Q
D
D
Q
Q
D
D
Din
D...
Dout
D...
Dms
D...
clk
c...
clk
clk
Din
Din
Dms
Dms
Dout
Dout
Text is not SVG - cannot display
\ No newline at end of file diff --git a/.pic/Labs/lab_10_irq/tab_02.png b/.pic/Labs/lab_10_irq/tab_02.png new file mode 100644 index 00000000..4eced63f Binary files /dev/null and b/.pic/Labs/lab_10_irq/tab_02.png differ diff --git a/.pic/Labs/lab_10_irq/tab_05.png b/.pic/Labs/lab_10_irq/tab_05.png deleted file mode 100644 index be0c96cc..00000000 Binary files a/.pic/Labs/lab_10_irq/tab_05.png and /dev/null differ diff --git a/.pic/Labs/lab_10_irq/table_2.xlsx b/.pic/Labs/lab_10_irq/table_2.xlsx new file mode 100644 index 00000000..03874ce4 Binary files /dev/null and b/.pic/Labs/lab_10_irq/table_2.xlsx differ diff --git a/.pic/Labs/lab_13_periph/fig_02.xlsx b/.pic/Labs/lab_13_periph/fig_02.xlsx deleted file mode 100644 index ecf3ea8c..00000000 Binary files a/.pic/Labs/lab_13_periph/fig_02.xlsx and /dev/null differ diff --git a/.pic/Labs/lab_13_periph/fig_05.png b/.pic/Labs/lab_13_periph/fig_03.png similarity index 100% rename from .pic/Labs/lab_13_periph/fig_05.png rename to .pic/Labs/lab_13_periph/fig_03.png diff --git a/.pic/Labs/lab_13_periph/fig_04.png b/.pic/Labs/lab_13_periph/fig_04.png index 94b94460..6a53cfe9 100644 Binary files a/.pic/Labs/lab_13_periph/fig_04.png and b/.pic/Labs/lab_13_periph/fig_04.png differ diff --git a/.pic/Labs/lab_13_periph/fig_06.png b/.pic/Labs/lab_13_periph/fig_06.png deleted file mode 100644 index 6a53cfe9..00000000 Binary files a/.pic/Labs/lab_13_periph/fig_06.png and /dev/null differ diff --git a/.pic/Labs/lab_13_periph/fig_02.png b/.pic/Labs/lab_13_periph/tab_01.png similarity index 100% rename from .pic/Labs/lab_13_periph/fig_02.png rename to .pic/Labs/lab_13_periph/tab_01.png diff --git a/.pic/Labs/lab_13_periph/tab_01.xlsx b/.pic/Labs/lab_13_periph/tab_01.xlsx new file mode 100644 index 00000000..5e76273a Binary files /dev/null and b/.pic/Labs/lab_13_periph/tab_01.xlsx differ diff --git a/.pic/Labs/lab_13_periph/tab_08.png b/.pic/Labs/lab_13_periph/tab_08.png new file mode 100644 index 00000000..94b94460 Binary files /dev/null and b/.pic/Labs/lab_13_periph/tab_08.png differ diff --git a/.pic/Labs/lab_13_periph/tab_08.xlsx b/.pic/Labs/lab_13_periph/tab_08.xlsx new file mode 100644 index 00000000..bf458fda Binary files /dev/null and b/.pic/Labs/lab_13_periph/tab_08.xlsx differ diff --git a/.pic/Labs/lab_15_programming_device/fig_01.svg b/.pic/Labs/lab_15_programming_device/fig_01.svg new file mode 100644 index 00000000..b8677777 --- /dev/null +++ b/.pic/Labs/lab_15_programming_device/fig_01.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + 鎖咗Locked + Un- + 過得locked + 銀仔Coin + 銀仔Coin + Push + Push + + diff --git a/.pic/Other/rv32i/tab_03.xlsx b/.pic/Other/rv32i/tab_03.xlsx new file mode 100644 index 00000000..b49f84ca Binary files /dev/null and b/.pic/Other/rv32i/tab_03.xlsx differ diff --git a/.pic/Vivado Basics/01. New project/fig_01.png b/.pic/Vivado Basics/01. New project/fig_01.png new file mode 100644 index 00000000..0bdac197 Binary files /dev/null and b/.pic/Vivado Basics/01. New project/fig_01.png differ diff --git a/.pic/Vivado Basics/01. New project/fig_02.png b/.pic/Vivado Basics/01. New project/fig_02.png new file mode 100644 index 00000000..74511a18 Binary files /dev/null and b/.pic/Vivado Basics/01. New project/fig_02.png differ diff --git a/.pic/Vivado Basics/01. New project/fig_03.png b/.pic/Vivado Basics/01. New project/fig_03.png new file mode 100644 index 00000000..04d347f1 Binary files /dev/null and b/.pic/Vivado Basics/01. New project/fig_03.png differ diff --git a/.pic/Vivado Basics/02. Flow Navigator/fig_01.png b/.pic/Vivado Basics/02. Flow Navigator/fig_01.png new file mode 100644 index 00000000..22f9676b Binary files /dev/null and b/.pic/Vivado Basics/02. Flow Navigator/fig_01.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_01.png b/.pic/Vivado Basics/03. Project manager/fig_01.png new file mode 100644 index 00000000..dd41119f Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_01.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_02.png b/.pic/Vivado Basics/03. Project manager/fig_02.png new file mode 100644 index 00000000..ad1964d7 Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_02.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_03.png b/.pic/Vivado Basics/03. Project manager/fig_03.png new file mode 100644 index 00000000..dfdbdf9c Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_03.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_04.png b/.pic/Vivado Basics/03. Project manager/fig_04.png new file mode 100644 index 00000000..86744856 Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_04.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_05.png b/.pic/Vivado Basics/03. Project manager/fig_05.png new file mode 100644 index 00000000..c3299f23 Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_05.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_06.png b/.pic/Vivado Basics/03. Project manager/fig_06.png new file mode 100644 index 00000000..7af74014 Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_06.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_07.png b/.pic/Vivado Basics/03. Project manager/fig_07.png new file mode 100644 index 00000000..7bdb40a9 Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_07.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_08.png b/.pic/Vivado Basics/03. Project manager/fig_08.png new file mode 100644 index 00000000..2e1f7ce9 Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_08.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_09.png b/.pic/Vivado Basics/03. Project manager/fig_09.png new file mode 100644 index 00000000..df3498e0 Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_09.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_10.png b/.pic/Vivado Basics/03. Project manager/fig_10.png new file mode 100644 index 00000000..12ce3338 Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_10.png differ diff --git a/.pic/Vivado Basics/03. Project manager/fig_11.png b/.pic/Vivado Basics/03. Project manager/fig_11.png new file mode 100644 index 00000000..d50c1f3d Binary files /dev/null and b/.pic/Vivado Basics/03. Project manager/fig_11.png differ diff --git a/.pic/Vivado Basics/04. Simulation/fig_01.png b/.pic/Vivado Basics/04. Simulation/fig_01.png new file mode 100644 index 00000000..f81420fc Binary files /dev/null and b/.pic/Vivado Basics/04. Simulation/fig_01.png differ diff --git a/.pic/Vivado Basics/04. Simulation/fig_02.png b/.pic/Vivado Basics/04. Simulation/fig_02.png new file mode 100644 index 00000000..70043f2a Binary files /dev/null and b/.pic/Vivado Basics/04. Simulation/fig_02.png differ diff --git a/.pic/Vivado Basics/04. Simulation/fig_03.png b/.pic/Vivado Basics/04. Simulation/fig_03.png new file mode 100644 index 00000000..6d891539 Binary files /dev/null and b/.pic/Vivado Basics/04. Simulation/fig_03.png differ diff --git a/.pic/Vivado Basics/04. Simulation/fig_04.png b/.pic/Vivado Basics/04. Simulation/fig_04.png new file mode 100644 index 00000000..fa82e548 Binary files /dev/null and b/.pic/Vivado Basics/04. Simulation/fig_04.png differ diff --git a/.pic/Vivado Basics/Debug manual/fig_01.png b/.pic/Vivado Basics/05. Bug hunting/fig_01.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_01.png rename to .pic/Vivado Basics/05. Bug hunting/fig_01.png diff --git a/.pic/Vivado Basics/Debug manual/fig_02.png b/.pic/Vivado Basics/05. Bug hunting/fig_02.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_02.png rename to .pic/Vivado Basics/05. Bug hunting/fig_02.png diff --git a/.pic/Vivado Basics/Debug manual/fig_03.png b/.pic/Vivado Basics/05. Bug hunting/fig_03.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_03.png rename to .pic/Vivado Basics/05. Bug hunting/fig_03.png diff --git a/.pic/Vivado Basics/Debug manual/fig_04.png b/.pic/Vivado Basics/05. Bug hunting/fig_04.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_04.png rename to .pic/Vivado Basics/05. Bug hunting/fig_04.png diff --git a/.pic/Vivado Basics/Debug manual/fig_05.png b/.pic/Vivado Basics/05. Bug hunting/fig_05.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_05.png rename to .pic/Vivado Basics/05. Bug hunting/fig_05.png diff --git a/.pic/Vivado Basics/Debug manual/fig_06.png b/.pic/Vivado Basics/05. Bug hunting/fig_06.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_06.png rename to .pic/Vivado Basics/05. Bug hunting/fig_06.png diff --git a/.pic/Vivado Basics/Debug manual/fig_07.png b/.pic/Vivado Basics/05. Bug hunting/fig_07.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_07.png rename to .pic/Vivado Basics/05. Bug hunting/fig_07.png diff --git a/.pic/Vivado Basics/Debug manual/fig_08.png b/.pic/Vivado Basics/05. Bug hunting/fig_08.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_08.png rename to .pic/Vivado Basics/05. Bug hunting/fig_08.png diff --git a/.pic/Vivado Basics/Debug manual/fig_09.png b/.pic/Vivado Basics/05. Bug hunting/fig_09.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_09.png rename to .pic/Vivado Basics/05. Bug hunting/fig_09.png diff --git a/.pic/Vivado Basics/Debug manual/fig_10.png b/.pic/Vivado Basics/05. Bug hunting/fig_10.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_10.png rename to .pic/Vivado Basics/05. Bug hunting/fig_10.png diff --git a/.pic/Vivado Basics/Debug manual/fig_11.png b/.pic/Vivado Basics/05. Bug hunting/fig_11.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_11.png rename to .pic/Vivado Basics/05. Bug hunting/fig_11.png diff --git a/.pic/Vivado Basics/Debug manual/fig_12.png b/.pic/Vivado Basics/05. Bug hunting/fig_12.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_12.png rename to .pic/Vivado Basics/05. Bug hunting/fig_12.png diff --git a/.pic/Vivado Basics/Debug manual/fig_13.png b/.pic/Vivado Basics/05. Bug hunting/fig_13.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_13.png rename to .pic/Vivado Basics/05. Bug hunting/fig_13.png diff --git a/.pic/Vivado Basics/Debug manual/fig_14.png b/.pic/Vivado Basics/05. Bug hunting/fig_14.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_14.png rename to .pic/Vivado Basics/05. Bug hunting/fig_14.png diff --git a/.pic/Vivado Basics/Debug manual/fig_15.png b/.pic/Vivado Basics/05. Bug hunting/fig_15.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_15.png rename to .pic/Vivado Basics/05. Bug hunting/fig_15.png diff --git a/.pic/Vivado Basics/Debug manual/fig_16.png b/.pic/Vivado Basics/05. Bug hunting/fig_16.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_16.png rename to .pic/Vivado Basics/05. Bug hunting/fig_16.png diff --git a/.pic/Vivado Basics/Debug manual/fig_17.png b/.pic/Vivado Basics/05. Bug hunting/fig_17.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_17.png rename to .pic/Vivado Basics/05. Bug hunting/fig_17.png diff --git a/.pic/Vivado Basics/Debug manual/fig_18.png b/.pic/Vivado Basics/05. Bug hunting/fig_18.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_18.png rename to .pic/Vivado Basics/05. Bug hunting/fig_18.png diff --git a/.pic/Vivado Basics/Debug manual/fig_19.png b/.pic/Vivado Basics/05. Bug hunting/fig_19.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_19.png rename to .pic/Vivado Basics/05. Bug hunting/fig_19.png diff --git a/.pic/Vivado Basics/Debug manual/fig_20.png b/.pic/Vivado Basics/05. Bug hunting/fig_20.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_20.png rename to .pic/Vivado Basics/05. Bug hunting/fig_20.png diff --git a/.pic/Vivado Basics/Debug manual/fig_21.png b/.pic/Vivado Basics/05. Bug hunting/fig_21.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_21.png rename to .pic/Vivado Basics/05. Bug hunting/fig_21.png diff --git a/.pic/Vivado Basics/Debug manual/fig_22.png b/.pic/Vivado Basics/05. Bug hunting/fig_22.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_22.png rename to .pic/Vivado Basics/05. Bug hunting/fig_22.png diff --git a/.pic/Vivado Basics/Debug manual/fig_23.png b/.pic/Vivado Basics/05. Bug hunting/fig_23.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_23.png rename to .pic/Vivado Basics/05. Bug hunting/fig_23.png diff --git a/.pic/Vivado Basics/Debug manual/fig_24.png b/.pic/Vivado Basics/05. Bug hunting/fig_24.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_24.png rename to .pic/Vivado Basics/05. Bug hunting/fig_24.png diff --git a/.pic/Vivado Basics/Debug manual/fig_25.png b/.pic/Vivado Basics/05. Bug hunting/fig_25.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_25.png rename to .pic/Vivado Basics/05. Bug hunting/fig_25.png diff --git a/.pic/Vivado Basics/Debug manual/fig_26.png b/.pic/Vivado Basics/05. Bug hunting/fig_26.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_26.png rename to .pic/Vivado Basics/05. Bug hunting/fig_26.png diff --git a/.pic/Vivado Basics/Debug manual/fig_27.png b/.pic/Vivado Basics/05. Bug hunting/fig_27.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_27.png rename to .pic/Vivado Basics/05. Bug hunting/fig_27.png diff --git a/.pic/Vivado Basics/Debug manual/fig_28.png b/.pic/Vivado Basics/05. Bug hunting/fig_28.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_28.png rename to .pic/Vivado Basics/05. Bug hunting/fig_28.png diff --git a/.pic/Vivado Basics/Debug manual/fig_29.png b/.pic/Vivado Basics/05. Bug hunting/fig_29.png similarity index 100% rename from .pic/Vivado Basics/Debug manual/fig_29.png rename to .pic/Vivado Basics/05. Bug hunting/fig_29.png diff --git a/.pic/Vivado Basics/06. RTL Analysis/fig_1.png b/.pic/Vivado Basics/06. RTL Analysis/fig_1.png new file mode 100644 index 00000000..48d7ce86 Binary files /dev/null and b/.pic/Vivado Basics/06. RTL Analysis/fig_1.png differ diff --git a/.pic/Vivado Basics/06. RTL Analysis/fig_2.png b/.pic/Vivado Basics/06. RTL Analysis/fig_2.png new file mode 100644 index 00000000..f25390f1 Binary files /dev/null and b/.pic/Vivado Basics/06. RTL Analysis/fig_2.png differ diff --git a/.pic/Vivado Basics/06. RTL Analysis/fig_3.png b/.pic/Vivado Basics/06. RTL Analysis/fig_3.png new file mode 100644 index 00000000..b28cd22f Binary files /dev/null and b/.pic/Vivado Basics/06. RTL Analysis/fig_3.png differ diff --git a/.pic/Vivado Basics/06. RTL Analysis/fig_4.png b/.pic/Vivado Basics/06. RTL Analysis/fig_4.png new file mode 100644 index 00000000..e9255755 Binary files /dev/null and b/.pic/Vivado Basics/06. RTL Analysis/fig_4.png differ diff --git a/.pic/Vivado Basics/07. Program and debug/fig_1.png b/.pic/Vivado Basics/07. Program and debug/fig_1.png new file mode 100644 index 00000000..0dfca52c Binary files /dev/null and b/.pic/Vivado Basics/07. Program and debug/fig_1.png differ diff --git a/.pic/Vivado Basics/08. Code processing errors/fig_01.png b/.pic/Vivado Basics/08. Code processing errors/fig_01.png new file mode 100644 index 00000000..0216e6e9 Binary files /dev/null and b/.pic/Vivado Basics/08. Code processing errors/fig_01.png differ diff --git a/.pic/Vivado Basics/08. Code processing errors/fig_02.png b/.pic/Vivado Basics/08. Code processing errors/fig_02.png new file mode 100644 index 00000000..7c6e6943 Binary files /dev/null and b/.pic/Vivado Basics/08. Code processing errors/fig_02.png differ diff --git a/.pic/Vivado Basics/08. Code processing errors/fig_03.png b/.pic/Vivado Basics/08. Code processing errors/fig_03.png new file mode 100644 index 00000000..6d3a1fbd Binary files /dev/null and b/.pic/Vivado Basics/08. Code processing errors/fig_03.png differ diff --git a/.pic/Vivado Basics/Elaboration failed/err_log.png b/.pic/Vivado Basics/Elaboration failed/err_log.png deleted file mode 100644 index a4b5be5f..00000000 Binary files a/.pic/Vivado Basics/Elaboration failed/err_log.png and /dev/null differ diff --git a/.pic/Vivado Basics/Elaboration failed/simFail.png b/.pic/Vivado Basics/Elaboration failed/simFail.png deleted file mode 100644 index 21fa3992..00000000 Binary files a/.pic/Vivado Basics/Elaboration failed/simFail.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to add a mem-file/how_to_mem_1.png b/.pic/Vivado Basics/How to add a mem-file/how_to_mem_1.png deleted file mode 100644 index d11c33a1..00000000 Binary files a/.pic/Vivado Basics/How to add a mem-file/how_to_mem_1.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to add a mem-file/how_to_mem_2.png b/.pic/Vivado Basics/How to add a mem-file/how_to_mem_2.png deleted file mode 100644 index c6ea4a50..00000000 Binary files a/.pic/Vivado Basics/How to add a mem-file/how_to_mem_2.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to add a mem-file/how_to_mem_3.png b/.pic/Vivado Basics/How to add a mem-file/how_to_mem_3.png deleted file mode 100644 index b4c645d7..00000000 Binary files a/.pic/Vivado Basics/How to add a mem-file/how_to_mem_3.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to add a mem-file/how_to_mem_4.png b/.pic/Vivado Basics/How to add a mem-file/how_to_mem_4.png deleted file mode 100644 index a2a5ecf1..00000000 Binary files a/.pic/Vivado Basics/How to add a mem-file/how_to_mem_4.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to open a schematic/fig_1.png b/.pic/Vivado Basics/How to open a schematic/fig_1.png deleted file mode 100644 index 0a6ca0a7..00000000 Binary files a/.pic/Vivado Basics/How to open a schematic/fig_1.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to open a schematic/fig_2.png b/.pic/Vivado Basics/How to open a schematic/fig_2.png deleted file mode 100644 index 09b3906a..00000000 Binary files a/.pic/Vivado Basics/How to open a schematic/fig_2.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to open a schematic/fig_3.png b/.pic/Vivado Basics/How to open a schematic/fig_3.png deleted file mode 100644 index caa717ac..00000000 Binary files a/.pic/Vivado Basics/How to open a schematic/fig_3.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to open a schematic/fig_4.png b/.pic/Vivado Basics/How to open a schematic/fig_4.png deleted file mode 100644 index 04deba9c..00000000 Binary files a/.pic/Vivado Basics/How to open a schematic/fig_4.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to open a schematic/fig_5.png b/.pic/Vivado Basics/How to open a schematic/fig_5.png deleted file mode 100644 index 014ac583..00000000 Binary files a/.pic/Vivado Basics/How to open a schematic/fig_5.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to program an fpga board/fig_1.png b/.pic/Vivado Basics/How to program an fpga board/fig_1.png deleted file mode 100644 index 39f16e4a..00000000 Binary files a/.pic/Vivado Basics/How to program an fpga board/fig_1.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to program an fpga board/fig_2.png b/.pic/Vivado Basics/How to program an fpga board/fig_2.png deleted file mode 100644 index c0796724..00000000 Binary files a/.pic/Vivado Basics/How to program an fpga board/fig_2.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_01.png b/.pic/Vivado Basics/How to use Source Window/fig_01.png deleted file mode 100644 index b489de92..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_01.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_02.png b/.pic/Vivado Basics/How to use Source Window/fig_02.png deleted file mode 100644 index 80aba2da..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_02.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_03.png b/.pic/Vivado Basics/How to use Source Window/fig_03.png deleted file mode 100644 index e59f5731..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_03.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_04.png b/.pic/Vivado Basics/How to use Source Window/fig_04.png deleted file mode 100644 index d2a26772..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_04.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_05.png b/.pic/Vivado Basics/How to use Source Window/fig_05.png deleted file mode 100644 index 662301e9..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_05.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_06.png b/.pic/Vivado Basics/How to use Source Window/fig_06.png deleted file mode 100644 index 357cf3bb..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_06.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_07.png b/.pic/Vivado Basics/How to use Source Window/fig_07.png deleted file mode 100644 index 2c4ddfb9..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_07.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_08.png b/.pic/Vivado Basics/How to use Source Window/fig_08.png deleted file mode 100644 index b2ad0c09..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_08.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_09.png b/.pic/Vivado Basics/How to use Source Window/fig_09.png deleted file mode 100644 index 82a67386..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_09.png and /dev/null differ diff --git a/.pic/Vivado Basics/How to use Source Window/fig_10.png b/.pic/Vivado Basics/How to use Source Window/fig_10.png deleted file mode 100644 index 89e0fae9..00000000 Binary files a/.pic/Vivado Basics/How to use Source Window/fig_10.png and /dev/null differ diff --git a/.pic/Vivado Basics/Run Simulation/fig_1.png b/.pic/Vivado Basics/Run Simulation/fig_1.png deleted file mode 100644 index abfc1e94..00000000 Binary files a/.pic/Vivado Basics/Run Simulation/fig_1.png and /dev/null differ diff --git a/.pic/Vivado Basics/Run Simulation/fig_2.png b/.pic/Vivado Basics/Run Simulation/fig_2.png deleted file mode 100644 index 72984ac5..00000000 Binary files a/.pic/Vivado Basics/Run Simulation/fig_2.png and /dev/null differ diff --git a/.pic/Vivado Basics/Run Simulation/fig_3.png b/.pic/Vivado Basics/Run Simulation/fig_3.png deleted file mode 100644 index c91529cd..00000000 Binary files a/.pic/Vivado Basics/Run Simulation/fig_3.png and /dev/null differ diff --git a/.pic/Vivado Basics/Verilog Header/Verilog_Header1.png b/.pic/Vivado Basics/Verilog Header/Verilog_Header1.png deleted file mode 100644 index e38a4323..00000000 Binary files a/.pic/Vivado Basics/Verilog Header/Verilog_Header1.png and /dev/null differ diff --git a/.pic/Vivado Basics/Verilog Header/Verilog_Header2.png b/.pic/Vivado Basics/Verilog Header/Verilog_Header2.png deleted file mode 100644 index 10a9e521..00000000 Binary files a/.pic/Vivado Basics/Verilog Header/Verilog_Header2.png and /dev/null differ diff --git a/.pic/Vivado Basics/Verilog Header/Verilog_Header3.png b/.pic/Vivado Basics/Verilog Header/Verilog_Header3.png deleted file mode 100644 index 81308b9d..00000000 Binary files a/.pic/Vivado Basics/Verilog Header/Verilog_Header3.png and /dev/null differ diff --git a/.pic/Vivado Basics/Verilog Header/Verilog_Header4.png b/.pic/Vivado Basics/Verilog Header/Verilog_Header4.png deleted file mode 100644 index ddb0886c..00000000 Binary files a/.pic/Vivado Basics/Verilog Header/Verilog_Header4.png and /dev/null differ diff --git a/.pic/Vivado Basics/Vivado trainer/fig_01.png b/.pic/Vivado Basics/Vivado trainer/fig_01.png deleted file mode 100644 index 6ea20a1d..00000000 Binary files a/.pic/Vivado Basics/Vivado trainer/fig_01.png and /dev/null differ diff --git a/Basic Verilog structures/Assignments.md b/Basic Verilog structures/Assignments.md index bf3544b0..25e8d459 100644 --- a/Basic Verilog structures/Assignments.md +++ b/Basic Verilog structures/Assignments.md @@ -7,21 +7,21 @@ Давайте разберемся что это за присваивания и почему необходимо руководствоваться этими правилами. -Начать придется издалека. Несмотря на то, что SystemVerilog является **языком описания аппаратуры**, он так же является и языком для верификации описанной аппаратуры (слово `Verilog` является объединением двух слов: `verification` и `logic`). Для целей верификации в языке выделено целое подмножество конструкций, которые не могут быть использованы для описания аппаратуры — так называемое "_несинтезируемое подмножество языка SystemVerilog_". Разумеется, часть языка, которая может быть использована для описания аппаратуры ("_синтезируемое подмножество языка SystemVerilog_") тоже может использоваться в верификации. +Начать придется издалека. Несмотря на то, что SystemVerilog является **языком описания аппаратуры**, он так же является и языком для верификации описанной аппаратуры (слово `Verilog` является объединением двух слов: `verification` и `logic` [2, стр. 24]). Для целей верификации в языке выделено целое подмножество конструкций, которые не могут быть использованы для описания аппаратуры — так называемое "_несинтезируемое подмножество языка SystemVerilog_". Разумеется, часть языка, которая может быть использована для описания аппаратуры ("_синтезируемое подмножество языка SystemVerilog_") тоже может использоваться в верификации. -Давайте для начала разберемся в том, как будут использоваться операторы присваивания при программном моделировании (так называемой симуляции) — одним из инструментов верификации. Разобравшись в поведении операторов во время симуляции будет куда проще объяснить результат использования операторов при синтезе цифровой схемы. +Давайте для начала разберемся в том, как будут использоваться операторы присваивания при программном моделировании (так называемой симуляции) — одним из инструментов верификации. Разобравшись в поведении операторов во время симуляции, будет куда проще объяснить результат использования операторов при синтезе цифровой схемы. Введем пару сокращений для удобства дальнейшего повествования: - под `LHS` (left hand side) мы будем подразумевать "выражение, **которому** присваивают"; -- под `RHS` (right hand side) мы будем подразумевать "выражение **которое** присваивают". +- под `RHS` (right hand side) мы будем подразумевать "выражение, **которое** присваивают". В выражении `a = b+c`, `a` является `LHS`, `b+c` является `RHS`. - два вида присваиваний: **непрерывное** и **процедурное**. +Существует два вида присваиваний: **непрерывное** и **процедурное**. -```SystemVerilog -module assignment_example( +```Verilog +module example_1( input logic a, b output logic c, d ); @@ -49,7 +49,7 @@ _Листинг 1. Пример непрерывного и процедурно С точки зрения моделирования (не описания аппаратуры), программный блок — это программа (в привычном вам понимании парадигмы программирования), исполняющаяся в отдельном процессе. Программные блоки исполняются независимо друг от друга по определенным событиям. -Блоки `initial` (их может быть много) исполняются в момент начала моделирования. Блоки `always` исполняются по событиям указанным в **списке чувствительности**: +Блоки `initial` (их может быть много) исполняются в момент начала моделирования. Блоки `always` исполняются по событиям, указанным в **списке чувствительности**: - `always @(posedge clk)` будет исполняться каждый раз когда произойдет положительный фронт `clk`; - `always @(a,b,c)` будет исполняться каждый раз, когда изменится значение любого из сигналов `a`,`b`,`c`; @@ -89,11 +89,11 @@ _Рисунок 2. Пример цепочки неблокирующих при 2. Затем вычисляется значение `RHS` второго присваивания. Поскольку `a` еще не присвоили значение `5`, результатом `RHS` становится текущее значение `a` — 3. Присваивание этого значения сигналу `b` **откладывается** на потом. 3. Аналогичным образом вычисляется `RHS` третьего присваивания (`2`). Присваивание этого значения также **откладывается** на потом. -Так называемое "**потом**" наступает когда завершается вычисление `RHS` всех неблокирующих присваиваний и завершение присвоений всех блокирующих присваиваний (однако "потом" все равно происходит в тот же момент времени, обратите внимание на значение времени на _рис. 2_). В стандарте SystemVerilog этот момент называется `NBA-region` (сокр. от "Non-Blocking Assignment region") [[2, стр. 61]](https://ieeexplore.ieee.org/document/8299595). Выполнение отложенных присваиваний происходит в том же порядке, в котором они шли в программном блоке. Подробнее о том как работает событийная симуляция (event based simulation) в SystemVerilog вы можете прочесть в стандарте [IEEE 1800-2017](https://ieeexplore.ieee.org/document/8299595) (раздел 4). Стандарт доступен бесплатно всем желающим по программе "IEEE GET Program". +Так называемое "**потом**" наступает, когда завершается вычисление `RHS` всех неблокирующих присваиваний и завершение присвоений всех блокирующих присваиваний (однако "потом" все равно происходит в тот же момент времени, обратите внимание на значение времени на _рис. 2_). В стандарте SystemVerilog этот момент называется `NBA-region` (сокр. от "Non-Blocking Assignment region") [[2, стр. 61]](https://ieeexplore.ieee.org/document/10458102). Выполнение отложенных присваиваний происходит в том же порядке, в котором они шли в программном блоке. Подробнее о том как, работает событийная симуляция (event based simulation) в SystemVerilog, вы можете прочесть в стандарте [IEEE 1800-2023](https://ieeexplore.ieee.org/document/10458102) (раздел 4). Стандарт доступен бесплатно всем желающим по программе "IEEE GET Program". Таким образом, если `LHS` **блокирующего** присваивания используется в качестве операнда `RHS` любого другого последующего присваивания, это выражение будет иметь уже обновленное значение, что очень похоже на "_последовательное вычисление_". -С другой стороны значение, присвоенное `LHS` значение с помощью **неблокирующего** присваивания не может использоваться в качестве операнда `RHS` последующих присваиваний, что создает иллюзию "_параллельного вычисления_" (см. _рис. 3_). +С другой стороны значение, присвоенное `LHS` значение с помощью **неблокирующего** присваивания, не может использоваться в качестве операнда `RHS` последующих присваиваний, что создает иллюзию "_параллельного вычисления_" (см. _рис. 3_). ![../.pic/Basic%20Verilog%20structures/assignments/fig_03.drawio.svg](../.pic/Basic%20Verilog%20structures/assignments/fig_03.drawio.svg) @@ -101,20 +101,20 @@ _Рисунок 3. Иллюстрация блокирующих и неблок Теперь, понимая как работают присваивания с точки зрения моделирования, посмотрим на то, во что могут синтезироваться подобные операторы. -Начнем с непрерывного присваивания. Оно превращается в провод, передающий данные от `RHS` к `LHS`. При этом вы должны контролировать что к чему вы присваиваете (не путайте местами `RHS` и `LHS`). +Начнем с непрерывного присваивания. Оно превращается в провод, передающий данные от `RHS` к `LHS`. При этом вы должны следить за тем, **что** и **чему** вы присваиваете (не путайте местами `RHS` и `LHS`). -То во что синтезируются блокирующие и неблокирующие присваивания зависит от описываемой логики, поэтому давайте разберем несколько примеров. +То, во что синтезируются блокирующие и неблокирующие присваивания зависит от описываемой логики, поэтому давайте разберём несколько примеров. Начнем с исходного примера c цепочкой блокирующих присваиваний, только теперь перепишем его в синтезируемом виде, сохранив изначальную идею. -```SystemVerilog -module example_1( +```Verilog +module example_2( input logic clk, input logic [31:0] in, output logic [31:0] out ); -logic [31:0] a,b,c; +logic [31:0] a, b, c; always_ff @(posedge clk) begin a = in; @@ -133,7 +133,7 @@ _Листинг 2. Пример описания модуля, использу --- -Давайте "прочитаем" эту схему. Мы видим модуль, с входом `in`, выходом `out` и тактирующим синхроимпульсом `clk`. Также мы видим три сигнала `a`,`b`,`c`, которые описываются в блоке `always_ff`, предназначенном для описания регистров. Значение `in` по цепочке этих регистров передается до регистра `c`, выход которого подключен к выходу `out`. +Давайте "прочитаем" эту схему. Мы видим модуль, с входом `in`, выходом `out` и тактирующим синхроимпульсом `clk`. Также мы видим три сигнала `a`, `b`, `c`, которые описываются в блоке `always_ff`, предназначенном для описания регистров. Значение `in` по цепочке этих регистров передается до регистра `c`, выход которого подключен к выходу `out`. Похоже, что здесь был описан [**сдвиговый регистр**](https://ru.wikipedia.org/wiki/Регистр_(цифровая_техника)#Сдвигающие_(последовательные)_регистры), представленный на _рис. 4_. @@ -151,7 +151,7 @@ _Рисунок 5. Схема, сгенерированная Vivado по опи Изучим внимательней поведение цепочки блокирующих присваиваний, представленное на _рис. 1_. -Каждое последующее присваивание ожидало, пока не выполнится предыдущее, таким образом, `RHS` первого присваивания (`5`) сразу же распространился по всем регистрам. Моделируя _Листинг 2_ мы получим **поведение**, когда на вход каждого регистра будет подаваться сигнал `in`. +Каждое последующее присваивание ожидало, пока не выполнится предыдущее, таким образом, `RHS` первого присваивания (`5`) сразу же распространился по всем регистрам. Моделируя _Листинг 2_, мы получим **поведение**, когда на вход каждого регистра будет подаваться сигнал `in`. Таким образом на самом деле, мы должны были изобразить нашу схему как на _рис. 6_. @@ -159,7 +159,7 @@ _Рисунок 5. Схема, сгенерированная Vivado по опи _Рисунок 6. Схема, описанная Листингом 2._ -Но почему тогда на схеме Vivado не осталось регистров `a` и `b`? Посмотрим на них внимательней. Их выходы ни на что не влияют, они висят неподключенные. А значит эти регистры не имеют никакого смысла и если их убрать, ничего не изменится. +Но почему тогда на схеме Vivado не осталось регистров `a` и `b`? Посмотрим на них внимательней. Их выходы ни на что не влияют, они ни к чему не подключены. А значит эти регистры не имеют никакого смысла и, если их убрать, ничего не изменится. При генерации схемы, Vivado вывел в `Tcl Console` следующие предупреждения: @@ -178,8 +178,8 @@ _Рисунок 7. Пример вызова линтера._ Давайте заменим в _Листинге 2_ блокирующие присваивания на неблокирующие. Напоминаем, что оператор неблокирующего присваивания записывается как `<=`. -```SystemVerilog -module example_2( +```Verilog +module example_3( input logic clk, input logic [31:0] in, output logic [31:0] out @@ -214,8 +214,8 @@ _Рисунок 8. Схема, сгенерированная Vivado по опи Можно ли реализовать сдвиговый регистр, используя блокирующие присваивания? Конечно. Например, можно поменять порядок присваиваний как в _Листинге 4_. -```SystemVerilog -module example_3( +```Verilog +module example_4( input logic clk, input logic [31:0] in, output logic [31:0] out @@ -244,8 +244,8 @@ _Листинг 4. Цепочка блокирующих присваивани Давайте разнесем логику работы каждого регистра по отдельным блокам `always`. -```SystemVerilog -module example_4( +```Verilog +module example_5( input logic clk, input logic [31:0] in, output logic [31:0] out @@ -292,14 +292,14 @@ _Рисунок 9. Симуляция модуля, описанного Лис _Рисунок 10. Моделирование поведения сдвигового регистра._ -Однако, как уже объяснялось ранее, вы не можете рассчитывать на такой результат. Сегодня симулятор смоделировал поведение одним образом — завтра он смоделирует этот же код (в котором не изменилась ни одна строка) по-другому, и будет по прежнему работать в соответствии со стандартом. +Однако, как уже объяснялось ранее, вы не можете рассчитывать на такой результат. Сегодня симулятор смоделировал поведение одним образом — завтра он смоделирует этот же код (в котором не изменилась ни одна строка) по-другому, и будет по-прежнему работать в соответствии со стандартом. Для того, чтобы получить детерминированный результат, вам необходимо снова воспользоваться неблокирующим присваиванием, поскольку и в этом случае порядок исполнения блоков `always` не влияет на результат присваиваний — сначала вычисляются значения `RHS` всех неблокирующих присваиваний всех программных блоков, и только потом происходит присваивание этих значений `LHS`. Рассмотрим еще один пример того, как различие в присваиваниях приведет к описанию двух различных схем: -```SystemVerilog -module example_5( +```Verilog +module example_6( input logic clk, input logic a, b, c, output logic d @@ -349,8 +349,8 @@ _Рисунок 12. Схема, сгенерированная Vivado по оп Рассмотрим зависимость от типа присваивания в комбинационных схемах. Для этого возьмем предыдущий пример, и уберем тактирующий синхроимпульс. -```SystemVerilog -module example_6( +```Verilog +module example_7( input logic a, b, c, output logic d ); @@ -413,12 +413,14 @@ _Рисунок 14. Моделирование цепочки присваива Обратите внимание, поведение схем описанных при разных типах присваивания слегка различаются. При блокирующем присваивании все сигналы приняли установившиеся значения за один проход блока `always`, при неблокирующем потребовалось несколько проходов. Однако с точки зрения пользователя, читающего временную диаграмму, в обоих ситуациях сигналы изменили свое значение мгновенно. -Поэтому не смотря на различия в типах присваиваний схемы получились одинаковыми. +Поэтому несмотря на различия в типах присваиваний схемы получились одинаковыми. > Получается что для комбинационной логики нет разницы между блокирующим и неблокирующим присваиванием, после переходных процессов результат будет одинаковым? И да и нет. С точки зрения синтеза схемы так и есть. Однако есть нюанс в случае моделирования схемы. Поведение комбинационной логики лучше моделирует блокирующее присваивание[[1, стр. 14]](http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf). +## Итоги главы + Подведем итоги прочитанному: - Блокирующее присваивание блокирует выполнение остальных операций до завершения текущего присваивания. Оно подобно обычному присваиванию в парадигме программирования. @@ -435,7 +437,7 @@ _Рисунок 14. Моделирование цепочки присваива - _Не смешивайте в одном блоке блокирующие и неблокирующие присваивания_ — стандарт допускает подобное описание, но оно затрудняет его чтение. Представьте, что читая описание схемы, вам бы постоянно приходилось держать в голове какие присваивания уже произошли, а какие только произойдут, чтобы понять как эта схема работает. - _Не смешивайте блокирующие и неблокирующие присваивания для одного и того же сигнала_ — стандарт это запрещает (для блоков `always_ff`, `always_comb`, `always_latch`). -Использованная литература: +## Список источников 1. [Clifford E. Cummings / Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill](http://www.sunburst-design.com/papers/CummingsSNUG2000SJ_NBA.pdf) 2. [1800-2017 - IEEE Standard for SystemVerilog--Unified Hardware Design, Specification, and Verification Language](https://ieeexplore.ieee.org/document/8299595) diff --git a/Basic Verilog structures/Common mistakes.md b/Basic Verilog structures/Common mistakes.md new file mode 100644 index 00000000..9a885afd --- /dev/null +++ b/Basic Verilog structures/Common mistakes.md @@ -0,0 +1,104 @@ +# Список типичных ошибок в SystemVerilog + +- [Список типичных ошибок в SystemVerilog](#список-типичных-ошибок-в-systemverilog) + - [имя сигнала is not a type](#имя-сигнала-is-not-a-type) + - [cannot find port on this module](#cannot-find-port-on-this-module) + - [Использование сигнала без его объявления (или до его объявления)](#использование-сигнала-без-его-объявления-или-до-его-объявления) + - [Объявление выхода модуля его входом](#объявление-выхода-модуля-его-входом) + - [](#) + +## имя сигнала is not a type + +Скорее всего, компилятор не распознал присваивание, поскольку оно было записано с ошибками. Вне блоков `always` и `initial` можно выполнять только непрерывное присваивание (через `assign`). + +```Verilog +module half_adder(input logic a, input logic b, output logic c); +c = a ^ b; // ошибка, для непрерывного присваивания + // необходимо ключевое слово assign +endmodule +``` + +_Листинг 1. Пример ошибочного присваивания._ + +## cannot find port on this module + +Имя порта, указанного при подключении модуля (после точки) не соответствует ни одному имени сигналов подключаемого модуля + +```Verilog +module half_adder(input logic a, input logic b, output logic c); + assign c = a ^ b; +endmodule + +module testbench(); +logic A, B, C; + +adder DUT( + .A(A), // <- здесь будет ошибка, + // т.к. в модуле half_adder нет порта 'A' + .b(B), + .c(C) +); +endmodule +``` + +_Листинг 2. Пример создание экземпляра модуля с ошибкой в описании его входных портов._ + +## Использование сигнала без его объявления (или до его объявления) + +Достаточно частая ошибка, когда сигнал забывают объявить, либо объявляют, но потом добавляют код с использованием этого сигнала выше его объявления. Не смотря на то, что в SystemVerilog не важно, в каком порядке были описаны блоки кода, это не касается объявления сигналов — они должны выполняться до их использования. Рассмотрим следующий пример, приведенный в _листинге 3_. + +```Verilog +module example( + input logic [1:0] a, + input logic [1:0] b, + output logic [1:0] c +); + + assign ab = a | b; + // logic [1:0] ab; + assign c = ab; +endmodule + +module tb_example(); + logic [1:0] a, b, c; + logic [3:0] i = 0; + example DUT(.a(a),.b(b),.c(c)); + assign {a, b} = i; + + initial repeat(15) begin + #5; + i++; + end +endmodule +``` + +_Литсинг 3. Пример присваивания значения сигналу без его объявления._ + +Результат моделирования _листинга 3_ приведён на _рисунке 1_. + +![../.pic/Basic%20Verilog%20structures/common%20mistakes/figure_01.png](../.pic/Basic%20Verilog%20structures/common%20mistakes/figure_01.png) + +Как вы можете увидеть, код был успешно собрался и был промоделирован, но значения в выделенных синим прямоугольниках не те, что должны были быть. Можно заметить также и то, что значение выхода `c` никогда не превышает единицу. + +Если мы начнем разбираться, и решим вытащить на временную диаграмму внутренний сигнал модуля `ab`, мы увидим, что он почему-то однобитный. Более того, если раскомментировать строчку с объявлением сигнала `ab`, результат никак не изменится. + +Подобное поведение в точности воспроизводит требование стандарта SystemVerilog [1, стр. 108]: + +> - Если идентификатор использовался списке сигналов, подключаемых к модулю, и этот идентификатор не был объявлен в области видимости, которая доступна при этом подключении, то неявно подразумевается однобитный провод. +> - Если идентификатор встречается по левую сторону от оператора непрерывного присваивания, и этот идентификатор не был объявлен в той области видимости, которая доступна оператору, то неявно подразумевается однобитный провод. + +Иными словами, если вы присваиваете значение необъявленному сигналу, или пытаетесь подключить необъявленный сигнал к модулю, неявно создается однобитный сигнал с тем же именем (попробуйте удалить объявление сигналов `a`, `b`, `c` в тестбенче _листинга 3_ и посмотрите, как изменится разрядность сигналов тестбенча на временной диаграмме). + +Даже если объявите сигнал с правильной разрядностью после его использования — это уже ничему не поможет, поскольку повторные объявления уже объявленных проводов и регистров стандартом запрещены [1, стр. 91] и в зависимости от САПР будут либо проигнорированы, либо вызовут ошибку. + +Подобная ошибка легко обнаруживается линтером (появился в Vivado начиная с версии 2023.1). Кроме того, при попытке открыть схематик описанной схемы, в TCL-консоли появится сообщение об использовании необъявленного идентификатора: + +```text +INFO: [Synth 8-11241] undeclared symbol 'ab', assumed default net type 'wire' +``` + +## Объявление выхода модуля его входом + +Очень часто в попытке сэкономить себе немного времени студенты выполняют операцию копирования. В частности, копирования строк вида `input logic [7:0]` в процессе описания портов модуля. В случае, если по итогу подобного копирования, выход модуля будет объявлен как его вход (т.е. с помощью ключевого слова `input` вместо `output`). + +## \ No newline at end of file diff --git a/Basic Verilog structures/Concatenation.md b/Basic Verilog structures/Concatenation.md index 8e670257..e9663afe 100644 --- a/Basic Verilog structures/Concatenation.md +++ b/Basic Verilog structures/Concatenation.md @@ -8,7 +8,7 @@ ![../.pic/Basic%20Verilog%20structures/concatenation/fig_01.drawio.svg](../.pic/Basic%20Verilog%20structures/concatenation/fig_01.drawio.svg) -```SystemVerilog +```Verilog logic a; logic b; @@ -29,7 +29,7 @@ logic [5:0] e; Это можно сделать путем 4 непрерывных присваиваний: -```SystemVerilog +```Verilog logic a; logic b; logic [7:0] c; @@ -45,7 +45,7 @@ assign e[1:0] = d; либо через одно присваивание, использующее конкатенацию: -```SystemVerilog +```Verilog logic a; logic b; logic [7:0] c; @@ -60,7 +60,7 @@ assign e = {a, b, c[4:3], d}; ![../.pic/Basic%20Verilog%20structures/concatenation/fig_02.drawio.svg](../.pic/Basic%20Verilog%20structures/concatenation/fig_02.drawio.svg) -```SystemVerilog +```Verilog logic a; logic b; logic [7:0] c; @@ -76,7 +76,7 @@ assign d = e[1:0]; Подобную операцию можно так же выполнить в одно выражение через конкатенацию: -```SystemVerilog +```Verilog logic a; logic b; logic [7:0] c; @@ -89,14 +89,14 @@ assign {a, b, c[4:3], d} = e; Кроме того, конкатенация может использоваться при **множественном дублировании** сигналов. Дублирование выполняется выражением: -```SystemVerilog +```Verilog {a, {число_повторений{повторяемый_сигнал}} ,b} ``` Допустим, мы хотим присвоить какому-то сигналу три копии `[4:3]` битов сигнала `c`, после которых идут сигналы `a` и `b`. Это можно сделать выражением: -```SystemVerilog +```Verilog logic a; logic b; logic [7:0] c; @@ -105,3 +105,10 @@ logic [7:0] e; assign e = { {3{c[4:3]}}, a, b}; ``` + +## Итоги главы + +Оператор конкатенации может быть использован для группировки и репликации сигналов по обе стороны присваивания, а именно: + +- он может быть использован для присваивания сигналу большей разрядности группы сигналов меньшей разрядности +- он может быть использован для присваивания группе сигналов меньшей разрядности соответствующих бит сигнала большей разрядности. diff --git a/Basic Verilog structures/Controllers.md b/Basic Verilog structures/Controllers.md index a3e96408..b89e9455 100644 --- a/Basic Verilog structures/Controllers.md +++ b/Basic Verilog structures/Controllers.md @@ -40,7 +40,7 @@ светодиоды являются простейшим устройством вывода. Поэтому, чтобы задание было интересней, для их управления был добавлен регистр, управляющий режимом вывода данных на светодиоды. Рассмотрим прототип модуля, который вам необходимо реализовать: -```SystemVerilog +```Verilog module led_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к системной шине diff --git a/Basic Verilog structures/Latches.md b/Basic Verilog structures/Latches.md index 8c7ec1c4..c94892a2 100644 --- a/Basic Verilog structures/Latches.md +++ b/Basic Verilog structures/Latches.md @@ -8,7 +8,7 @@ Обычно появление защелки в цифровой схеме говорит об ошибке разработки: в случае, если планировалась комбинационная логика, добавление защелки приведет к непредвиденному удержанию предыдущих значений (поскольку защелка сохраняет предыдущее значение до прихода очередной комбинации управляющего сигнала, описанной в блоке `case`). Это особенно плохо, если сигнал, перед которым появилась защелка, чем-то управляет. Представьте, что он управляет сейфом, который должен открываться если ввели правильный пароль: -```SystemVerilog +```Verilog always_comb begin if(password_is_correct) begin open_the_safe <= 1'b1; @@ -18,7 +18,7 @@ end Вроде бы вы все описали правильно: "если ввели правильный пароль — сейф откроется". Однако проблема в том, что это состояние сохранится, и сейф уже не закроется. Именно поэтому у каждого блока `if` должен быть свой блок `else`: -```SystemVerilog +```Verilog always_comb begin if(password_is_correct) begin open_the_safe <= 1'b1; @@ -33,7 +33,7 @@ end Ещё один пример: -```SystemVerilog +```Verilog module unexpected_d_latch_ex( input logic [1:0] S, input logic D0, diff --git a/Basic Verilog structures/Modules.md b/Basic Verilog structures/Modules.md index 542413d6..2d40e7ad 100644 --- a/Basic Verilog structures/Modules.md +++ b/Basic Verilog structures/Modules.md @@ -12,7 +12,7 @@ ![../.pic/Basic%20Verilog%20structures/modules/fig_01.drawio.svg](../.pic/Basic%20Verilog%20structures/modules/fig_01.drawio.svg) -```SystemVerilog +```Verilog module @@ -23,7 +23,7 @@ endmodule ![../.pic/Basic%20Verilog%20structures/modules/fig_02.drawio.svg](../.pic/Basic%20Verilog%20structures/modules/fig_02.drawio.svg) -```SystemVerilog +```Verilog module box(); @@ -34,7 +34,7 @@ endmodule ![../.pic/Basic%20Verilog%20structures/modules/fig_03.drawio.svg](../.pic/Basic%20Verilog%20structures/modules/fig_03.drawio.svg) -```SystemVerilog +```Verilog module box( input logic a, input logic b, @@ -49,7 +49,7 @@ endmodule ![../.pic/Basic%20Verilog%20structures/modules/fig_04.drawio.svg](../.pic/Basic%20Verilog%20structures/modules/fig_04.drawio.svg) -```SystemVerilog +```Verilog module box( input logic a, input logic b, @@ -62,13 +62,13 @@ module box( endmodule ``` -Для объявления провода `c` использовалось ключевое слово (тип) `logic`. Этот тип, подобно стволовым клеткам, может быть в конечном итоге привести к созданию как ячеек памяти (регистров), так и проводов, в зависимости от того, как было описано присваивание объекту этого типа. Поэтому в примере выше говорить о том, что был создан провод не совсем корректно, объект схемы `c` станет проводом, когда будет произведено подключение к этому объекту, соответствующее подключению провода. +Для объявления провода `c` использовалось ключевое слово (тип) `logic`. Этот тип может в конечном итоге привести к созданию как ячеек памяти (регистров), так и проводов, в зависимости от того, как было описано присваивание объекту этого типа (подобно тому как стволовые клетки организма могут дифференцироваться в специализированные клетки в зависимости от ситуации). Поэтому в примере выше говорить о том, что был создан провод не совсем корректно, объект схемы `c` станет проводом, когда будет произведено подключение к этому объекту, соответствующее подключению провода. Подключим провод `c` ко входу `a`. Для этого используется конструкция `assign c = a;`. Такая конструкция называется **непрерывным присваиванием**. Если очень сильно упростить, то непрерывное присваивание схоже со спайкой двух проводов. После подобного присваивания, провод `c` всегда будет иметь то же значение, что и `a` — как только входной сигнал `a` изменит свое значение, внутренний провод `c` также изменит свое значение (проводу `c` будет **непрерывно присваиваться** значение входа `a`). ![../.pic/Basic%20Verilog%20structures/modules/fig_05.drawio.svg](../.pic/Basic%20Verilog%20structures/modules/fig_05.drawio.svg) -```SystemVerilog +```Verilog module box( input logic a, input logic b, @@ -95,7 +95,7 @@ endmodule Такую схему можно реализовать следующим описанием: -```SystemVerilog +```Verilog module box( input logic a, input logic b, @@ -114,7 +114,7 @@ endmodule ![../.pic/Basic%20Verilog%20structures/modules/fig_07.drawio.svg](../.pic/Basic%20Verilog%20structures/modules/fig_07.drawio.svg) -```SystemVerilog +```Verilog module box( input logic a, input logic b, @@ -155,14 +155,18 @@ endmodule Используя индекс, можно обратиться к отдельным битам вектора. С помощью диапазона индексов можно получить доступ к диапазону соответствующих битов. -|фрагмент кода|описание| -|-------------|--------| -|sum[0]; | Обращение к младшему биту вектора sum, объявленного выше| -|sum[7:4]; | Обращение к старшим четырем битам 8-битного вектора sum, объявленного выше| +|фрагмент кода|описание | +|-------------|-------------------------------------------------------------------------| +|sum[0]; | Обращение к младшему биту вектора sum, объявленного выше | +|sum[7:5]; | Обращение к старшим трём битам 8-битного вектора sum, объявленного выше | +|sum[5+:3]; | Обращение к трём битам, начиная со пятого (т.е. это аналог предыдущего выражения, удобно использовать, когда известен начальный бит и их количество, а конечный нужно считать через них) | +|sum[7-:3]; | Обращение к трём битам, заканчивая седьмым (т.е. это аналог предыдущего выражения, удобно использовать, когда известен конечный бит и их количество, а начальный нужно считать через них) | -Важно понимать, что векторы могут быть использованы и при описании портов модуля: +_Таблица 1. Способы обращения как к отдельным битам вектора, так и к диапазонам его бит._ -```SystemVerilog +Векторы могут быть использованы и при описании портов модуля: + +```Verilog module vector_ex( input logic [3:0] a, // У данного модуля четырехразрядный вход 'a' output logic [7:0] b // и восьмиразрядный выход 'b'. @@ -186,19 +190,18 @@ endmodule Опишем `inv`: -```SystemVerilog +```Verilog module inv( input logic a, output logic d ); - assign d = ~a; endmodule ``` Опишем `top`: -```SystemVerilog +```Verilog module top( input logic a, input logic b, @@ -227,17 +230,7 @@ endmodule Тогда в нашем описании добавится подключение второго модуля `inv` и провод `c`. -```SystemVerilog -module inv( - input logic a, - output logic d -); - - assign d = ~a; -endmodule -``` - -```SystemVerilog +```Verilog module top( input logic a, input logic b, @@ -274,20 +267,20 @@ endmodule ___ -## Итоги +## Итоги главы 1. Ключевым блоком в иерархии цифровой схемы, описанной на языке SystemVerilog является **модуль**. Модули позволяют выносить части сложной цифровой схемы в отдельные блоки, из которых потом и будет составлена итоговая схема, что сильно упрощает разработку. 2. Условно, модуль можно разделить на следующие части: 1. Объявление модуля: 1. Ключевые слова `module` / `endmodule` определяющие границы описания модуля. 2. Название модуля, следующее за ключевым словом `module`. Описанный модуль представляет собой отдельный тип, имя которого совпадает с названием модуля. - 3. Указание входов и выходов (портов) модуля, идущих в круглых скобках после названия модуля. Для указания направления порта модуля используются ключевые слова `input` и `output`. После указание направления порта следует указать тип порта (в рамках данного курса типом портов всегда будет logic), его разрядность, а затем имя. + 3. Указание входов и выходов (портов) модуля, идущих в круглых скобках после названия модуля. Для указания направления порта модуля используются ключевые слова `input` и `output`. После указание направления порта следует указать тип порта (в рамках данного курса типом портов всегда будет `logic`), его разрядность, а затем имя. 2. Функциональное описание модуля: 1. Объявление внутренних сигналов модуля (будь то проводов или регистров) с помощью ключевого слова `logic`. 2. Создание при необходимости объектов других модулей. 3. Описание функциональной связи между различными сигналами и объектами внутри описываемого модуля. -## Проверь себя +## Проверьте себя Как, по-вашему, описать нижеприведенную схему на языке описания аппаратуры SystemVerilog? diff --git a/Basic Verilog structures/Multiplexors.md b/Basic Verilog structures/Multiplexors.md index e81d707a..8b9b39ce 100644 --- a/Basic Verilog structures/Multiplexors.md +++ b/Basic Verilog structures/Multiplexors.md @@ -1,4 +1,4 @@ -# Описание мультиплексора на языке SystemVerilog +# Описание мультиплексоров в SystemVerilog **Мультипле́ксор** — устройство, имеющее **несколько сигнальных входов**, **один или более управляющих входов** и **один выход**. Мультиплексор позволяет передавать сигнал **с одного из входов на выход**; при этом выбор желаемого входа осуществляется подачей соответствующей комбинации управляющих сигналов[[1]](https://ru.wikipedia.org/wiki/Мультиплексор_(электроника)). @@ -42,7 +42,7 @@ a = b+c >= 5 ? b+c : b+d; Сперва вычисляется первый операнд (выражение `b+c >= 5`). Если это выражение оказалось истинным (равно единице), то переменной `a` будет присвоено значение второго операнда (выражения `b+c`), в противном случае переменной `a` будет присвоено значение третьего операнда (выражения `b+d`). -```SystemVerilog +```Verilog logic Y; assign Y = S==1 ? D1 : D0; ``` @@ -61,7 +61,7 @@ assign Y = S==1 ? D1 : D0; ## Блок always -Блок `always` — это специальный блок, который позволяет описывать комбинационные и последовательностные схемы, используя более сложные конструкции, такие как `if-else`, `case`. На самом деле, в языке SystemVerilog помимо общего блока `always`, которым можно описать любой вид логики, существует множество специализированных блоков, предназначенных для описания отдельно комбинационной, синхронной и последовательностной асинхронной логики соответственно: +Блок `always` — это специальный блок, который позволяет описывать комбинационные и последовательностные схемы (см. документ "[Последовательностная логика](../Introduction/Sequential%20logic.md)"), используя более сложные конструкции, такие как `if-else`, `case`. На самом деле, в языке SystemVerilog помимо общего блока `always`, которым можно описать любой вид логики, существует множество специализированных блоков, предназначенных для описания отдельно комбинационной, синхронной и последовательностной асинхронной логики соответственно: - always_comb - always_ff @@ -73,7 +73,7 @@ assign Y = S==1 ? D1 : D0; - внутри блока `always_ff` и `always_latch` необходимо использовать оператор неблокирующего присваивания (`<=`); - внутри блока `always_comb` необходимо использовать оператор блокирующего присваивания (`=`). -- + --- > Остановитесь на выделенном выше фрагменте документа, пока полностью не разберете его. Без освоения всех описанных выше особенностей языка SystemVerilog вы столкнетесь в будущем с множеством ошибок. @@ -88,7 +88,7 @@ assign Y = S==1 ? D1 : D0; После, в блоке `else` описывается присваивание сигнала, который должен идти на выход при управляющем сигнале равном нулю (значение после оператора `:` в тернарном операторе). -```SystemVerilog +```Verilog logic Y; always_comb begin // 1) Используется always_comb, т.к. мы хотим подключить // выход мультиплексора к проводу @@ -104,7 +104,7 @@ end Неправильно: -```SystemVerilog +```Verilog logic Y; always_comb begin if(S==1) begin @@ -135,7 +135,7 @@ end Реализация двухвходового мультиплексора с помощью `case` может выглядеть так: -```SystemVerilog +```Verilog logic Y; always_comb begin case(S) // Описываем блок case, где значение сигнала S @@ -150,9 +150,9 @@ end // (так же как каждый begin должен ок ![../.pic/Basic%20Verilog%20structures/multiplexors/fig_04.drawio.svg](../.pic/Basic%20Verilog%20structures/multiplexors/fig_04.drawio.svg) -Здесь уже используется мультиплексор 4в1. Управляющий сигнал `S` в данном случае двухбитный. В блоке `case` мы перечисляем всевозможные варианты значений `S` и описываем выход мультиплексора. +Здесь уже используется мультиплексор 8в1. Управляющий сигнал `S` в данном случае трёхбитный. В блоке `case` мы перечисляем всевозможные варианты значений `S` и описываем выход мультиплексора. -```SystemVerilog +```Verilog module case_mux_ex( input logic A, input logic B, @@ -192,7 +192,7 @@ endmodule В контексте примера по мультиплексированию 1024 бит использование оператора может быть выполнено следующим образом: -```SystemVerilog +```Verilog logic [1023:0] bus1024; logic [ 9:0] select; @@ -203,7 +203,7 @@ assign one_bit_result = bus1024[select]; Реализация мультиплексоров через оператор '[]' будет активно применяться вами при реализации различных памятей. -## Итоги +## Итоги главы 1. Мультиплексор — это **комбинационный** блок, подающий на выход один из нескольких входных сигналов. 2. Мультиплексор можно описать множеством способов, среди них: diff --git a/Basic Verilog structures/Registers.md b/Basic Verilog structures/Registers.md index 9d45f86e..ecdab2de 100644 --- a/Basic Verilog structures/Registers.md +++ b/Basic Verilog structures/Registers.md @@ -1,8 +1,8 @@ -# Описание регистра на языке SystemVerilog +# Описание регистров в SystemVerilog Перед тем, как описывать память, необходимо научиться описывать отдельные регистры. [Регистр](https://ru.wikipedia.org/wiki/Регистр_(цифровая_техника)) — это базовая ячейка памяти, позволяющая хранить состояние, пока на схему подается питание. В современной электронике, регистр чаще всего строится на D-триггерах. В лабораторной работе по АЛУ уже вскользь упоминалось, что как для описания проводов, так и для описания регистров, используется тип `logic`. -```SystemVerilog +```Verilog logic reg_name; ``` @@ -26,16 +26,16 @@ logic reg_name; Данной схеме соответствует код: -```SystemVerilog -modulе rеg_ехаmрlе( - inрut logic clk, - inрut logic dаtа, - оutрut logic rеg_dаtа +```Verilog +module reg_example( + input logic clk, + input logic data, + output logic reg_data ); - logic rеg_nаmе; + logic reg_name; -еndmоdulе +endmodule ``` Очевидно, мы хотим подключить сигнал `clk` ко входу тактирующего сигнала регистра, вход `data` ко входу данных, а выход регистра к выходу `reg_data`: @@ -46,16 +46,16 @@ modulе rеg_ехаmрlе( Описание регистра, а также указание фронта и тактирующего сигнала происходит в конструкции `always_ff`: -```SystemVerilog -аlwауs_ff @(pоsеdgе clk) +```Verilog +always @(posedge clk) ``` Далее, внутри данной конструкции необходимо указать, что происходит с содержимым регистра. В нашем случае, происходит запись с входного сигнала `data` -```SystemVerilog -аlwауs_ff @(pоsеdgе clk) - rеg_nаmе <= dаtа; -еnd +```Verilog +always @(posedge clk) begin + reg_name <= data; +end ``` Обратите внимание на оператор `<=`. В данном случае, это не знак "меньше либо равно", а оператор **неблокирующего присваивания**. Существует оператор **блокирующего присваивания** (`=`), который меняет способ построения схемы для такого же выражения справа от оператора, однако в данный момент этот оператор останется за рамками курса. Хоть это и плохая практика в обучении, но пока вам надо просто запомнить, что **при описании записи в регистр всегда используйте оператор неблокирующего присваивания `<=`**. @@ -64,49 +64,49 @@ modulе rеg_ехаmрlе( Таким образом, итоговый код описания данной схемы примет вид: -```SystemVerilog -modulе rеg_ехаmрlе( - inрut logic сlk, - inрut logic dаtа, - оutрut logic rеg_dаtа +```Verilog +module reg_example( + input logic clk, + input logic data, + output logic reg_data ); - logic rеg_nаmе; + logic reg_name; - аlwауs_ff @(pоsеdgе clk) bеgin - rеg_nаmе <= dаtа; - еnd + always @(posedge clk) begin + reg_name <= data; + end - аssign reg_data = reg_name; + assign reg_data = reg_name; -еndmоdulе +endmodule ``` Предположим, мы хотим добавить управление записью в регистр через сигналы `enable` и `reset`. Это, например, можно сделать следующим образом: -```SystemVerilog -modulе rеg_ехаmрlе( - inрut logic сlk, - inрut logic dаtа, - inрut logic reset, - inрut logic enable, - оutрut logic rеg_dаtа +```Verilog +module reg_example( + input logic clk, + input logic data, + input logic reset, + input logic enable, + output logic reg_data ); - logic rеg_nаmе; + logic reg_name; - аlwауs_ff @(pоsеdgе clk) bеgin - if(rеsеt) bеgin - rеg_nаmе <= 1'b0; - еnd - еlse if(enable) bеgin - rеg_nаmе <= dаtа; - еnd - еnd + always_ff @(posedge clk) begin + if(reset) begin + reg_name <= 1'b0; + end + else if(enable) begin + reg_name <= data; + end + end - аssign rеg_dаtа = rеg_nаmе; + assign reg_data = reg_name; -еndmоdulе +endmodule ``` Обратите внимание на очередность условий. В первую очередь, мы проверяем условие **сброса**, и только после этого условие **разрешения на запись**. @@ -122,7 +122,7 @@ modulе rеg_ехаmрlе( **Присваивание регистру может выполняться только в одном блоке `always`** -Даже если вдруг, САПР не выдаст сразу сообщение об ошибке, в конечном итоге, на этапе синтеза схемы она рано или поздно появится в виде сообщения связанного с **"multiple drivers"**. +Даже если вдруг САПР не выдаст сразу сообщение об ошибке, в конечном итоге, на этапе синтеза схемы она рано или поздно появится в виде сообщения связанного с **"multiple drivers"**. В блоке присваивания регистру можно описывать и комбинационную логику, стоящую перед ним, например схему: @@ -130,50 +130,64 @@ modulе rеg_ехаmрlе( можно описать как -```SystemVerilog -modulе rеg_ехаmрlе( - inрut logic сlk, - inрut logic dаtа, +```Verilog +module reg_example( + input logic clk, input logic A, input logic B, - оutрut logic rеg_dаtа + input logic reset, + input logic enable, + output logic reg_data ); - logic rеg_nаmе; + logic reg_name; - аlwауs_ff @(pоsеdgе clk) bеgin - rеg_nаmе <= А & В; - еnd + always_ff @(posedge clk) begin + if(reset) begin + reg_name <= 1'b0; + end + else if(enable) begin + reg_name <= A & B; + end + end - аssign reg_data = reg_name; + assign reg_data = reg_name; -еndmоdulе +endmodule ``` Однако это всего лишь упрощение. Если вы умеете описывать регистр с подключением к нему всего одного провода на входе данных, вы все равно сможете описать эту схему: -```SystemVerilog -modulе rеg_ехаmрlе( - inрut logic сlk, - inрut logic А, - inрut logic В, - оutрut logic rеg_dаtа +```Verilog +module reg_example( + input logic clk, + input logic A, + input logic B, + input logic reset, + input logic enable, + output logic reg_data ); - logic rеg_nаmе; // Обратите внимание, что несмотря на то, что - logic аb; // и reg_name и ab объявлены типом logic, - // ab станет проводом, а reg_name — регистром + logic reg_name; // Обратите внимание, что несмотря на то, что + logic ab; // и reg_name и ab объявлены типом logic, + // ab станет проводом, а reg_name - регистром // (из-за непрерывного присваивания на ab, и блока // always_ff для reg_name) - аssign аb = А & В; - аlwауs_ff @(pоsеdgе clk) bеgin - rеg_nаmе <= аb; - еnd + assign ab = A & B; + + always_ff @(posedge clk) begin + if(reset) begin + reg_name <= 1'b0; + end + else if(enable) begin + reg_name <= ab; + end + end - аssign reg_data = reg_name; + assign reg_data = reg_name; -еndmоdulе +endmodule ``` Поэтому так важно разобраться в базовом способе описания регистра. @@ -183,25 +197,25 @@ modulе rеg_ехаmрlе( Вообще говоря, регистр в общем смысле этого слова представляет собой многоразрядную конструкцию (в рассмотренном ранее примере, однобитный регистр мог представлять из себя простой D-триггер). Создание многоразрядного регистра мало отличается от создания многоразрядного провода, а описание логики записи в многоразрядный регистр ничем не отличается от логики записи в одноразрядный регистр: -```SystemVerilog -modulе rеg_ехаmрlе( - inрut logic сlk, - inрut logic [7:0] dаtа, - оutрut logic [7:0] rеg_dаtа +```Verilog +module reg_example( + input logic clk, + input logic [7:0] data, + output logic [7:0] reg_data ); - logic [7:0] rеg_nаmе; + logic [7:0] reg_name; - аlwауs_ff @(pоsеdgе clk) bеgin - rеg_nаmе <= dаtа; - еnd + always_ff @(posedge clk) begin + reg_name <= data; + end - аssign reg_data = reg_name; + assign reg_data = reg_name; -еndmоdulе +endmodule ``` -## Итоги +## Итоги главы 1. [Регистр](https://ru.wikipedia.org/wiki/Регистр_(цифровая_техника)) — это базовая ячейка памяти, позволяющая хранить состояние, пока на схему подается питание. 2. Для объявления регистра используется тип `logic`, при необходимости после типа указывается разрядность будущего регистра. diff --git a/Introduction/How FPGA works.md b/Introduction/How FPGA works.md index b3ff7831..0987830a 100644 --- a/Introduction/How FPGA works.md +++ b/Introduction/How FPGA works.md @@ -12,8 +12,8 @@ - [Арифметика](#арифметика) - [Логическая ячейка](#логическая-ячейка) - [Сеть межсоединений](#сеть-межсоединений) - - [Выводы](#выводы) - - [Источники](#источники) + - [Итоги главы](#итоги-главы) + - [Список источников](#список-источников) > Разделы "Цифровые схемы и логические вентили" и "Таблицы подстановки" во многом используют материалы статьи "[How Does an FPGA Work?[1]](https://learn.sparkfun.com/tutorials/how-does-an-fpga-work/all)" за авторством `Alchitry, Ell C`, распространяемой по лицензии [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). @@ -37,6 +37,12 @@ ### Цифровые схемы +**Цифровая схема** — это **абстрактная модель** вычислений, которая оперирует двумя дискретными состояниями, обычно обозначаемыми как `0` и `1`. Важно понимать, что эти состояния не привязаны к конкретным физическим величинам, таким как напряжение в электрической цепи. Вместо этого они представляют собой обобщенные логические значения, которые могут быть реализованы на любой технологии, способной различать два четких состояния. + +Благодаря этой абстракции, цифровые схемы могут быть реализованы не только с помощью традиционных электронных компонентов, но и на совершенно иных платформах, например, на [пневматических системах](https://habr.com/ru/companies/ruvds/articles/692236/), [из картона и шариков](https://habr.com/ru/articles/399391/), [красной пыли](https://minecraft.fandom.com/wiki/Tutorials/Redstone_computers) в игре Майнкрафт или даже с использованием человеческого взаимодействия, подобно тому как это описано в романе Лю Цысиня "Задача трёх тел" (эффективность подобных схем — это уже другой вопрос). Основная идея заключается в том, что цифровая схема отвязывается от физической реализации, фокусируясь лишь на логике взаимодействия состояний `0` и `1`, что делает ее универсальной и независимой от конкретной технологии. + +Разумеется, при проектировании эффективных цифровых схем, необходимо оглядываться на технологию, по которой эти схемы будут работать. + В электронике, словом "цифровая" описывают схемы, которые абстрагируются от непрерывных (аналоговых) значений напряжений, вместо этого используется только два дискретных значения: `0` и `1`. На данном уровне абстракции нас не интересуют конкретные значения напряжений и пороги этих значений, что позволяет нам разрабатывать схему в идеальном мире, где у напряжения может быть всего два значения: `0` и `1`. А обеспечением этих условий будут заниматься базовые блоки, из которых мы будем строить цифровые схемы. Эти базовые блоки называются **логическими вентилями**. @@ -159,7 +165,7 @@ _Рисунок 12. Реализация мультиплексора, испо Из транзисторов можно построить не только логические элементы, но и элементы памяти. На рис. 13 представлена схема простейшей ячейки статической памяти, состоящей из транзистора и двух инверторов (т.е. суммарно состоящей из 5 транзисторов, поэтому она называется **5T** SRAM). Данная ячейка реализует 1 бит конфигурируемой памяти, являвшейся одним из основных компонентов самой первой ПЛИС. -![../.pic/Introduction/How%20FPGA%20works/fig_13.drawio.svg](../.pic/Introduction/How%20FPGA%20works/fig_13.drawio.svg) +![../.pic/Introduction/Sequential%20logic/fig_06.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_06.drawio.svg) _Рисунок 13. Конфигурируемая ячейка памяти ПЛИС Xilinx XC2064[[2, стр. 2-63](https://archive.org/details/programmablegate00xili/page/n93/mode/2up)]._ @@ -196,7 +202,7 @@ _Рисунок 14. Реализация таблицы подстановки ( **D-триггер** — это цифровой элемент, способный хранить один бит информации. В базовом варианте у этого элемента есть два входа и один выход. Один из входов подает значение, которое будет записано в **D-триггер**, второй вход управляет записью (обычно он называется `clk` или `clock` и подключается к тактирующему синхроимпульсу схемы). Когда управляющий сигнал меняет свое значение с `0` на `1` (либо с `1` на `0`, зависит от схемы), в **D-триггер** записывается значение сигнала данных. Обычно, описывая **D-триггер**, говорится, что он строится из двух **триггеров-защелок** (**D latch**), которые в свою очередь строятся из **RS-триггеров**, однако в конечном итоге, все эти элементы могут быть построены на базе логических вентилей **И**/**ИЛИ**, **НЕ**: -![../.pic/Introduction/How%20FPGA%20works/fig_15.drawio.svg](../.pic/Introduction/How%20FPGA%20works/fig_15.drawio.svg) +![../.pic/Introduction/Sequential%20logic/fig_05.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_05.drawio.svg) _Рисунок 15. Реализация D-триггера._ @@ -254,13 +260,13 @@ _Рисунок 18. Пример использования логической _Рисунок 19. Содержимое ПЛИС в виде межсоединения логических блоков и блоков ввода-вывода._ -Синим показано 9 логических блоков, желтым — 12 блоков ввода-вывода. Все эти блоки окружены **сетью межсоединений** (interconnect net), представляющей собой матрицу из горизонтальных и вертикальных соединительных линий — межсоединений общего назначения (general purpose interconnect)[[2, 2-66](https://archive.org/details/programmablegate00xili/page/n97/mode/2up)]. +Синим показано 9 логических блоков, желтым — 12 блоков ввода-вывода. Все эти блоки окружены **сетью межсоединений** (interconnect net), представляющей собой матрицу из горизонтальных и вертикальных соединительных линий — межсоединений общего назначения (general purpose interconnect) [[2, 2-66](https://archive.org/details/programmablegate00xili/page/n97/mode/2up)]. Косыми чертами в местах пересечения линий обозначены **программируемые точки межсоединений** (**programmable interconnect points**, **PIP**s), представляющие собой транзисторы, затвор которых подключен к программируемой памяти. Управляя значением в подключенной к затвору транзистора памяти, можно управлять тем, что из себя будет представлять транзистор в данной точке: разрыв, или цепь. А значит, можно удалять "лишние" участки сети, оставляя только используемые логические блоки, соединенные между собой. -## Выводы +## Итоги главы Обобщим сказанное: @@ -275,7 +281,7 @@ _Рисунок 19. Содержимое ПЛИС в виде межсоедин 9. Подключая такой бит конфигурируемой памяти к транзисторам, расположенных в узлах **сети межсоединений**, можно управлять тем, где в этой сети будут разрывы, а значит можно оставить только маршрут, по которому сигнал пойдет туда, куда нам нужно (**трассировать сигнал**). 10. **Конфигурируя примитивы** и **трассируя сигнал** между ними (см. п.4), можно получить **практически любую цифровую схему** (с учетом ограничения ёмкости ПЛИС). -## Источники +## Список источников 1. Alchitry, Ell C / [How Does an FPGA Work?](https://learn.sparkfun.com/tutorials/how-does-an-fpga-work/all) 2. Xilinx / [The Programmable Gate Array Data Book](https://archive.org/details/programmablegate00xili) diff --git a/Introduction/Implementation steps.md b/Introduction/Implementation steps.md index aa320bf0..313261ee 100644 --- a/Introduction/Implementation steps.md +++ b/Introduction/Implementation steps.md @@ -32,14 +32,14 @@ _Рисунок 1. Пример простой цифровой схемы._ ```SystemVerilog module sample( - input logic a, b, c, d, sel, - output logic res - ); + input logic a, b, c, d, sel, + output logic res +); - logic ab = a & b; - logic xabc = ab ^ c; +logic ab = a & b; +logic xabc = ab ^ c; - assign res = sel? d : xabc; +assign res = sel? d : xabc; endmodule ``` @@ -203,7 +203,7 @@ _Рисунок 6. Расположение выбранного LUT-а внут После того, как САПР определил конкретные примитивы, их режим работы, и пути сигнала между ними, необходимо создать двоичный файл (**bitstream**), который позволит сконфигурировать ПЛИС необходимым нам образом. Получив этот файл, остается запрограммировать ПЛИС, после чего она воплотит разработанное устройство. -## Выводы +## Итоги главы Таким образом, маршрут перехода от HDL-описания устройства до его реализации в ПЛИС выглядит следующим образом: @@ -221,6 +221,6 @@ _Рисунок 6. Расположение выбранного LUT-а внут Область допустимых решений для этапов "**place & route**" сужается путем наложения **физических** и **временны́х** **ограничений** (**constraints**). 4. Последним этапом выполняется **генерация двоичного файла конфигурации** (**bitstream generation**), который во время прошивки сконфигурирует ПЛИС на реализацию построенной схемы. -## Список использованной литературы +## Список источников 1. [Форум Xilinx: what exactly is 'elaborating' a design?](https://support.xilinx.com/s/question/0D52E00006iHshoSAC/what-exactly-is-elaborating-a-design?language=en_US) diff --git a/Introduction/README.md b/Introduction/README.md index 06aecbc1..df2789ef 100644 --- a/Introduction/README.md +++ b/Introduction/README.md @@ -4,12 +4,13 @@ Чем глубже вы проникнитесь содержимым этих документов, тем безболезненнее для вас пройдет процесс изучения дисциплины. -Начать необходимо с документа "[What is HDL](What%20is%20HDL.md)", рассказывающем о том, что такое **Языки Описания Аппаратуры** (**Hadrware Description Languages**, **HDL**). +Порядок чтения следующий: -Затем, для того чтобы закрепить понимание происходящего, вам предлагается прочитать документ "[How FPGA Works](./How%20FPGA%20works.md)", рассказывающий о том, как работает ПЛИС изнутри. +1. "[What is HDL](What%20is%20HDL.md)" — в документе описано, что такое **Языки Описания Аппаратуры** (**Hadrware Description Languages**, **HDL**). +2. "[How FPGA Works](./How%20FPGA%20works.md)" — в документе описано, как работает ПЛИС изнутри. +3. "[Sequential logic](./Sequential%20logic.md)" — в документе описана классификация цифровой логики (комбинационная / последовательностная), эволюция бистабильных ячеек от петли инверторов до D-триггера, явление метастабильности и критический путь. +4. "[Implementation Steps](./Implementation%20steps.md)" — в документе описано процесс реализации проекта от HDL-описания цифровой схемы до конфигурации этой схемой ПЛИС. Его прочтение даст большее понимание о принципе работы ПЛИС и позволит посмотреть на некоторые её реальные элементы изнутри. -И в довершение, вам предлагается прочитать документ "[Implementation Steps](./Implementation%20steps.md)". Этот документ дополнит ваше понимание о принципе работы ПЛИС и позволит посмотреть на некоторые её реальные элементы изнутри. - -Обратите внимание, что во втором абзаце не было использовано слово "поймете". Часто это слово несет не тот смысл. Можно прочесть документ и **понять** каждое его слово, но не постигнуть смысла, который в этих слова лежал (**слышать**, но не **слушать**). В романе Роберта Хайнлайна "_Чужак в чужой стране_" вводится особое марсианское слово, непереводимое на земной язык: "**грокать**", которое имеет множество значений. В первом приближении можно подумать, что это слово переводится как "понять", однако это не так. Например, на Марсе очень мало воды и процесс её питья марсианами (по сюжету романа, разумеется) является целым ритуалом, и обозначается этим же словом "грокать". Грокать что-то — означает что это что-то стало частью твоего естества. В отношении информации это означает, это информация стала частью тебя, изменила то, как ты думаешь. Грокать — это постичь что-то на самом глубинном уровне, это видеть девушку в красном сквозь завесу падающих зеленых символов. Даже этот абзац расписан для того, чтобы вы не просто поняли, что эти документы важно понять — а грокнули то, что эти документы важно грокнуть. +Обратите внимание, что во втором абзаце не было использовано слово "поймёте". Часто это слово несет не тот смысл. Можно прочесть документ и **понять** каждое его слово, но не постигнуть смысла, который в этих слова лежал (**слышать**, но не **слушать**). В романе Роберта Хайнлайна "_Чужак в чужой стране_" вводится особое марсианское слово, непереводимое на земной язык: "**грокать**", которое имеет множество значений. В первом приближении можно подумать, что это слово переводится как "понять", однако это не так. Например, на Марсе очень мало воды и процесс её питья марсианами (по сюжету романа, разумеется) является целым ритуалом, и обозначается этим же словом "грокать". Грокать что-то — означает что это что-то стало частью твоего естества. В отношении информации это означает, это информация стала частью тебя, изменила то, как ты думаешь. Грокать — это постичь что-то на самом глубинном уровне, это видеть девушку в красном сквозь завесу падающих зеленых символов. Даже этот абзац расписан для того, чтобы вы не просто поняли, что эти документы важно понять — а грокнули то, что эти документы важно грокнуть. На самом деле не важно, каким словом будет обозначен результат вашего прочтения. Важно то, что если после того как вы прочтете эти документы, на лабах вы будете употреблять словосочетания наподобие: "_объявляем переменную_", значит что-то пошло не так, и образ вашего мышления все еще заперт в парадигме "программирования". Это не то чтобы плохо, просто усложнит вам процесс изучения и выполнения лабораторных работ. diff --git a/Introduction/Sequential logic.md b/Introduction/Sequential logic.md new file mode 100644 index 00000000..96253153 --- /dev/null +++ b/Introduction/Sequential logic.md @@ -0,0 +1,182 @@ +# Последовательностная логика + +## Классификация цифровой логики + +Цифровая логика делится на **комбинационную** и **последовательностную**. + +**Комбинационная логика** (или "логика без памяти") — это цифровая логика, выходы которой зависят только от её входов. Один и тот же набор входных воздействий на эту логику всегда будет давать один и тот же результат. Комбинационную логику можно всегда представить в виде таблицы истинности (или логической функции) всех её выходов от её входов. + +В противоположность комбинационной, существует также и последовательностная логика, или "логика с памятью" — цифровая логика, выходы которой зависят не только от её входов, но и от её внутреннего состояния. + +Простейшим примером комбинационной логики может быть любой логический вентиль, например исключающее ИЛИ (_рис. 1 (а)_). Эта комбинационная схема всегда будет давать `0`, если оба её входа равны, в противном случае, она выдаст `1`. + +![../.pic/Introduction/Sequential%20logic/fig_01.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_01.drawio.svg) + +_Рисунок 1. Пример комбинационной (а), и последовательностной (б) схем._ + +Предположим теперь, что в качестве одного из входов исключающего ИЛИ стоит некая ячейка памяти, которая запоминает предыдущее значение, выданное этим логическим вентилем (_рис. 1 (б)_). Теперь, выходы схемы зависят не только от того, что мы подадим на вход, но и от того, что находится в данной ячейке памяти, а самое главное — теперь, подавая на вход одно и тоже воздействие, мы можем получить разные результаты. + +Будем исходить из того, что изначально ячейка памяти проинициализирована нулём. Сперва подадим на вход этой схемы `0`. Поскольку оба входа равны `0`, на выход схемы подаётся `0`, и значение в ячейке памяти остаётся прежним. Затем, подадим на вход `1` — теперь на выход схемы идёт значение `1` и оно же сохраняется в ячейке памяти. После, мы снова подаём на вход `0`, однако, в отличие от первого раза, на выход схемы пойдёт `1`, т.к. входы логического ИЛИ не равны. Выставив на вход `1` ещё раз, мы получим на выходе `0`. +Как вы видите, результат последовательностной логики зависит от **последовательности** произведённых входных воздействий, в то время как комбинационная логика зависит от **комбинации** её текущих входных воздействий. + +Последовательностная логика делится на **синхронную** и **асинхронную**. + +**Синхронной логикой** называется такая логика, обновляет своё состояние (содержимое ячеек памяти) одновременно (**синхронно**) с фронтом тактового сигнала. В свою очередь **асинхронная последовательностная логика** — это логика, которая может обновлять своё состояние **асинхронно** (т.е. без привязки к фронту тактового синхроимпульса). Бывает также и синхронная логика с асинхронными сигналами предустановки/сброса. + +Комбинационная логика по своей природе является асинхронной, поэтому в зависимости от контекста под "асинхронной логикой" может подразумеваться как комбинационная логика, так и последовательностная логика, которая может обновлять значение не по фронту тактового синхроимпульса. + +## Бистабильные ячейки + +**Статическая память** — это тип памяти, который сохраняет данные в течение неопределённого времени, пока его питание остаётся включённым, без необходимости регенерации (в отличие от **динамической памяти**, которая требует регулярного обновления данных для их сохранения). Основой статической памяти является **бистабильная ячейка** — элемент, способный сохранять одно из двух устойчивых состояний, соответствующих цифровым значениям «0» или «1». + +Рассмотрим простейшую ячейку статической памяти, представленную на _рис. 2_, которая способна хранить 1 бит информации. + +![../.pic/Introduction/Sequential%20logic/fig_02.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_02.drawio.svg) + +_Рисунок 2. Простейшая ячейка статической памяти._ + +Данная ячейка представляет собой петлю из двух инверторов, в которых "заперто" хранимое значение. Дважды инвертированный сигнал совпадает по значению с исходным, при этом проходя через каждый из инверторов, сигнал обновляет своё значение напряжения, поддерживая тем самым уровни напряжения логических значений. Главной проблемой подобной ячейки является то, что она требует дополнительной аппаратуры для записи в эту ячейку хранимой информации. + +Для того, чтобы добавить в эту ячейку входы с возможностью записи данных, проще всего будет поставить перед инверторами логические элементы ИЛИ (которые совместно инверторами образуют элементы ИЛИ-НЕ). + +В результате получится **RS-триггер** — бистабильная ячейка, представленная на _рис. 3_. + +## RS-триггер + +![../.pic/Introduction/Sequential%20logic/fig_03.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_03.drawio.svg) + +_Рисунок 3. Схема и таблица истинности RS-триггера. Q' означает, что на выход Q пойдет его предыдущее значение._ + +RS-триггер — это бистабильная ячейка, имеющая два управляющих входа: `R` (reset) и `S` (set), и два выхода: `Q` и `Q̅`. `Q̅` является инверсией `Q`. RS-триггер, построенный на логических элементах ИЛИ-НЕ, работает следующим образом: + +1. Если вход `R=1`, а `S=0`, то выход верхнего элемента ИЛИ-НЕ (а значит и выход `Q`) равен `0` вне зависимости от второго его входа. Этот выход поступает вместе с входом `S` на нижний элемент ИЛИ-НЕ, который выдаёт `1` (на выход `Q̅`), поскольку оба его входа равны `0`. Эта единица подаётся на второй вход верхнего элемента ИЛИ-НЕ и теперь, даже если вход `R` станет равным `0`, `1` на втором его входе сможет воспроизвести тоже самое поведение, запирая таким образом внутри триггера стабильное состояние `Q=0`. +2. Если вход `R=0`, а `S=1`, схема работает противоположным образом: поскольку на нижний элемент подаётся `1` с входа `S`, выход `Q̅` равен `0` вне зависимости от второго входа нижнего элемента ИЛИ-НЕ. Этот ноль подаётся на второй вход верхнего элемента ИЛИ-НЕ, и поскольку оба его входа равны `0`, на выходе этого элемента (на выход `Q`) подаётся `1`, которая возвращается обратно на вход нижнего элемента ИЛИ-НЕ, запирая таким образом внутри триггера стабильное состояние `Q=1`. +3. Таким образом, если на оба входа одновременно равны `0`, RS-триггер хранит своё предыдущее значение. + +Проблемой данного триггера является то, что он имеет **запрещённую** комбинацию входов. В случае RS-триггера, построенного на элементах ИЛИ-НЕ, таковой комбинацией входов является `R=1` и `S=1`. Даже с точки зрения функционального назначения, данная комбинация не имеет смысла: кому потребуется одновременно и сбрасывать RS-триггер в 0 и устанавливать его в 1? Тем не менее, вот что произойдет, если использовать эту комбинацию: + +4. Если оба входа одновременно равны `1`, то оба выхода Q и Q̅ будут равны `0`, что нарушает логику работы триггера, поскольку выход Q̅ должен быть инверсией выхода Q. При этом, если после этого перевести оба входа в `0`, RS-триггер окажется в неустойчивом состоянии (в состоянии гонки), а выходы могут начать неопределённо долго инвертироваться. Пока RS-триггер был в запрещённом состоянии, выходы `Q` и `Q̅`, равные `0`, подавались на входы обоих элементов ИЛИ-НЕ, а если после этого **одновременно** перевести входы `R` и `S` в состояние `0`, то на входах обоих вентилей будут `0`, что побудит их выдать на выходы `1`, которые пойдут обратно на входы этих вентилей, после чего те подадут на выход `0`, и так будет продолжаться до тех пор, пока один из сигналов в петле обратной связи не выиграет гонку, и RS-триггер не окажется в стабильном состоянии `0` либо `1`. + +Для того, чтобы избавиться от запрещённого состояния RS-триггера, была придумана D-защёлка (gated D-latch). + +## D-защелка + +D-защёлка — это бистабильная ячейка памяти, имеющая входы `D` (Data) и `E` (enable). Иногда вход enable называют clk (clock) или G (gated), что никак не сказывается на его функциональном назначении. Когда сигнал `E` равен `1`, D-защёлка "захватывает" данные с входа `D` (отсюда и её название). Когда сигнал `E` равен `0`, D-защёлка сохраняет уже захваченные данные. + +D-защёлка строится на базе RS-триггера, к которому добавляется логика, исключающая возможность появления запрещённого состояния (_рис. 4_). + +![../.pic/Introduction/Sequential%20logic/fig_04.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_04.drawio.svg) + +_Рисунок 4. Схема и таблица истинности D-защёлки._ + +Логика работы D-защёлки заключается в следующем. Когда сигнал `E` равен `0`, логические вентили И подают на выход `0` вне зависимости от второго входа, и RS-триггер переходит в состояние хранения текущего значения. В такой ситуации говорят, что D-защёлка "закрыта", или "перешла в непрозрачное состояние". Когда сигнал `E` равен `1`, логические элементы И, добавленные перед входами RS-триггера передают на выход значение со второго их входа. При этом на второй вход этих элементов подаются противоположные сигналы: `!D` и `D`, что исключает возможность одновременного появления `1` на входах `R` и `S`. В этом случае в RS-триггер попадает значение с входа `D`, а про D-защёлку говорят, что она "открыта" (перешла в "прозрачное" состояние). Пока защёлка "прозрачна", данные со входа `D` идут напрямую на выход `Q`. + +Несмотря на то, что D-защёлка устраняет главный недостаток RS-триггера, она тоже является не самой надёжной бистабильной ячейкой памяти. Дело в том, что D-защёлка пропускает на выход данные со входа `D` всё то время, пока она "прозрачна". Это значит, что она будет пропускать через себя все возможные переходные процессы сигнала `D`. Это значит, что она будет распространять переходные процессы сигналов со входа D, на которые будут реагировать последующие участки цифровой схемы. Таким образом через всю цифровую схему, начиная со входов, будут распространяться переходные процессы. В результате определить моменты времени, в которых на выходе схемы будет корректный результат обработки входного сигнала, станет практически невозможно. Было бы гораздо удобней, если бы могли сохранять данные одномоментно, когда на входе `D` уже находится установившееся значение, отсекая тем самым на каждом элементе памяти переходные процессы всех предыдущих участков цифровой схемы. + +Таким элементом памяти, является D-триггер (D flip-flop). + +## D-триггер + +D-триггер — это элемент статической памяти, который сохраняет данные со входа `D` **в момент перехода управляющего сигнала из нуля в единицу** (либо в момент перехода из единицы в ноль). Данный сигнал называется **сигналом синхронизации** (или **синхроимпульсом**) и обозначается как clk (clock). + +На _рис. 5_ показан способ построения D-триггера из двух D-защёлок. + +![../.pic/Introduction/Sequential%20logic/fig_05.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_05.drawio.svg) + +_Рисунок 5. Схема и таблица истинности D-триггера._ + +Принцип работы D-триггера, схема которого представлена на _рис. 5_ заключается в том, что управляющий сигнал `E` одной защёлки является инверсией управляющего сигнала `E` другой защёлки. Это значит, что пока одна защёлка "прозрачна" и принимает данные со входа — другая "непрозрачна" и данные не принимает. В момент, когда тактовый синхроимпульс меняет своё значение с `0` на `1`, ведущая защёлка становится "непрозрачной" для новых данных с входа `D`, и "запертые" в ней данные попадают в только что открывшуюся ведомую защёлку. Несмотря на то, что ведомая защёлка "прозрачна" всё то время, пока сигнал `clk = 0`, данные в ней остаются стабильными, поскольку выход ведущей защёлки больше не может измениться. + +Описанные схемы бистабильных ячеек представляют собой скорее математическое описание элементов памяти — так проще объяснить принцип их работы. Если ваша технология позволяет реализовать элементы И, ИЛИ и НЕ — значит вы точно можете реализовать подобные элементы. При этом, используя особенности конкретной технологии, данные схемы можно реализовывать более эффективно. D-защёлку, к примеру, можно реализовать схемой, представленной на _рис. 6_. + +![../.pic/Introduction/Sequential%20logic/fig_06.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_06.drawio.svg) + +_Рисунок 6. Конфигурируемая ячейка памяти ПЛИС Xilinx XC2064 [[1, стр. 2-63](https://archive.org/details/programmablegate00xili/page/n93/mode/2up)]._ + +## Метастабильность + +Как ранее упоминалось, при проектировании эффективных цифровых схем, необходимо оглядываться на аналоговые особенности технологии, по которой эти схемы будут реализованы. Выполним анализ простейшей бистабильной ячейки, построенной на двух инверторах. Для этого рассмотрим _рис. 7_. На _рис. 7 (а)_ показана передаточная функция Uвых = T(Uвх) некоторого инвертора. По оси абсцисс откладывается входное напряжение, подаваемое на инвертор, а по оси ординат — его выходное напряжение. Если подать на вход инвертора, описываемого подобной передаточной функцией напряжение, равное 0В (соответствует цифровому значению `0`), на выходе будет напряжение равное 3В (соответствует цифровому значению `1`), и наоборот: если подать на вход значение 3В, мы получим на выходе значение приблизительно равное 0В. + +Поскольку в бистабильной ячейке выход одного инвертора подаётся на вход второго, оказывается удобным наложить графики передаточной функции обоих таким образом, чтобы входное напряжение одного инвертора оказалось на той же оси, где откладывается выходное напряжение другого инвертора, как представлено на _рис. 7 (б)_. Точки пересечения кривых на этом графике являются точками равновесия, в которых входные и выходные напряжения обоих инверторов являются согласованными. + +![../.pic/Introduction/Sequential%20logic/fig_07.svg](../.pic/Introduction/Sequential%20logic/fig_07.svg) + +_Рисунок 7. Передаточные функции для: а) одиночного КМОП-инвертора; б) пары инверторов, объединённых в бистабильную петлю [2, стр. 497]._ + +Как вы можете заметить, таких точек почему-то не две, а три. Две эти точки обозначены как **стабильные** и соответствуют привычным цифровым значениям 1 (для 3В) и 0 (для 0В). Третья точка равновесия обозначена как **метастабильная** и расположена примерно посередине между этими двумя значениями. И действительно, согласно графику, если подать на вход приблизительно 1.5В, на выходе будет точно такое же напряжение, которое затем будет подано на вход второго инвертора и т.д., благодаря чему петля будет находиться в подобном состоянии неопределённый промежуток времени. Подобное состояние называется **метастабильным состоянием** и присуще любой бистабильной ячейке. + +Традиционно, для объяснения явления метастабильности используется аналогия с шариком на холме (_рис. 8_). Предположим, шарик находится у подножия левого склона холма. Если приложить к нему достаточную силу, направленную вправо — шарик перекатится через холм, и он окажется на противоположном склоне (для удобства аналогии, на склонах холма стоят стенки, чтобы шарик останавливался всегда в одной и той же точке этого склона). Если приложить недостаточно силы — шарик поднимется немного вверх по холму, и скатится обратно, остановившись в той же точке, откуда и начал. Однако, если вы будете достаточно "удачливы" и "точны", вы можете приложить ровно столько силы, чтобы шарик поднялся на вершину холма, но не смог с неё скатиться. Этот шарик может оставаться в таком положении неопределённое количество времени, но любое малейшее возмущение (будь то лёгкое дуновение ветерка, вызванное взмахом крыла пролетевшей рядом бабочки, или далёкое землетрясение, можете придумать свою экстравагантную причину) может заставить шарик скатиться в любую сторону. + +![../.pic/Introduction/Sequential%20logic/fig_08.svg](../.pic/Introduction/Sequential%20logic/fig_08.svg) + +_Рисунок 8. Механическая аналогия явлению метастабильности [2, стр. 498]._ + +Вернёмся к _рис. 7_ (б). Предположим, что инвертор находится в метастабильном состоянии и в цепи возникла случайная наводка, слегка отклонившая напряжение на входе одного из инверторов. Это отклонение усилится на выходе инвертора и попадёт на вход второго инвертора, усилившись на котором оно вернётся на вход первого инвертора и т.д. пока в конечном итоге не остановится в верхней равновесной точке. + +Если же возмущение произойдёт, пока бистабильная ячейка была в стабильном состоянии — её состояние не изменится. Предположим, что бистабильная ячейка хранит значение `1`, т.е. на вход первого инвертора подаётся значение 0В, и пришло возмущение, отклонившее это напряжение до 1В (а для подобной цифровой схемы это очень экстремальное отклонение, за пределами допустимых режимов работы). Проведём вертикальную линию до точки пересечения с черной кривой — это значение на выходе первого инвертора, и входа второго инвертора. Из этой точки, проведём горизонтальную линию до пересечения с синей кривой — это значение на выходе второго инвертора и входе первого. В общем-то, уже на этом этапе, на вход первого инвертора снова подаётся околонулевое напряжение. Именно поэтому крайние две точки пересечения называются **стабильными** — пока на схему подаётся питание, ячейка будет находиться в этом состоянии бесконечно долго до тех пор, пока не произойдёт существенного воздействия, чтобы она могла изменить это состояние. + +В случае метастабильного состояния — мы не можем предсказать, конкретное значение того, как долго ячейка будет находиться в этом состоянии — это случайная величина, для которой может быть оценено значение вероятности. Например, можно сделать оценку вроде: "вероятность того, что бистабильная ячейка выйдет из метастабильного состояния через 100мс много выше вероятности, что она выйдет из этого состояния через 100 секунд" + +Таким образом, метастабильность — это явление, возникающее входе нарушения условий работы цифровых элементов. В обычных случаях это явление является нежелательным (если только вы не планируете использовать свою схему в качестве генератора случайных чисел) и важно знать, как его избежать. + +Любые бистабильные ячейки имеют специальные временны́е параметры (ограничения), которые несоблюдение которых может привести к появлению метастабильности. В рамках этого курса, вы будете работать в основном с бистабильными ячейками, представленными в виде D-триггеров. Для D-триггеров таковыми временными параметрами являются: + +- Tsetup (**setup time**) — **время предустановки**. Это интервал, в течение которого сигнал на входе `D` должен оставаться неизменным перед наступлением фронта тактового сигнала. +- Thold (**hold time**) — **время удержания**. Это интервал, в течение которого сигнал на входе `D` должен оставаться стабильным после наступления фронта тактового сигнала. + +Эти два параметра образуют временное окно вокруг фронта тактового сигнала, в течение которого входной сигнал должен оставаться стабильным. Несоблюдение данных требований приводит к неопределённому поведению триггера (см. _рис. 9_). В простейшем случае он сохранит либо "старое", либо "новое" значение, пришедшее на вход данных D в непосредственной близости от фронта клока, но какое именно — неизвестно. Однако иногда "звёзды сойдутся", и триггер окажется в метастабильном состоянии. Вероятность этого крайне мала (о таком событии можно сказать, что оно "одно на миллиард"), однако не стоит относиться к нему с пренебрежением. Если схема работает на частоте в 1ГГц, триггер будет обновлять своё состояние миллиард раз в секунду, а сама схема может содержать миллионы триггеров. В таком контексте, фраза "одно на миллиард" означает не "ничего страшного, скорее при моей жизни этого не произойдёт", а "чёрт, кажется, что поэтому у меня ничего не работает". + +![../.pic/Introduction/Sequential%20logic/fig_09.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_09.drawio.svg) + +_Рисунок 9. Пример нарушения временны́х параметров D-триггера [[3](https://habr.com/ru/articles/254869/)]._ + +На _рис. 9_ показано три различных исхода нарушения временных ограничений: + +1. Выход триггера Q1 принял новое значение сигнала D, которое было установлено во временном промежутке Tsetup. +2. Выход триггера Q1 принял старое значение сигнала D, которое было установлено до на входе до начала Tsetup. На следующем положительном фронте clk на входе D находится уже установившееся значение, которое без проблем записывается в триггер. +3. Перемена в уровне во время Tsetup привело к тому, что на триггер было подано напряжение, равное половине уровня логической единицы, и тот оказался в метастабильном состоянии. Спустя некоторое время, триггер оказался в одном из стабильном состояний, но в каком — никто заранее предсказать не может (заштрихованная область, где триггер принял значение либо 0, либо 1). На следующем положительном фронте clk на входе D находится уже установившееся значение, которое без проблем записывается в триггер. + +Нарушение по Tsetup происходит, когда схема работает на частоте, не подходящей для имеющегося у схемы критического пути. Критический путь — это комбинационная часть цифровой схемы с наибольшей задержкой распространения сигнала. Время прохождения сигнала по этому пути характеризует минимально возможный период тактового сигнала и, соответственно, максимальную тактовую частоту работы всей схемы. + +Если подать на схему частоту, превышающую ограничение, определяемое критическим путём, сигнал может не принять установившееся значение на конце критического пути и, если на этом конце находится вход триггера — будут нарушено его ограничение по времени предустановки Tsetup. + +Нарушение по Thold происходит, когда у схемы есть пути с задержкой распространения сигнала до элементов последовательностной логики, которая меньше минимально допустимой. Данные пути напрямую не влияют на значение максимальной частоты, но требуют добавления элементов задержки на кратчайшие пути. Такие пути, как правило, являются значительно проблемой при проектировании ASIC. +Допустим в схеме есть два регистра А и Б, задержка распространения сигнала между которыми меньше допустимой. В этом случае, в момент фронта синхроимпульса с выхода регистра А может начать распространяться изменение в уровне сигнала. Это изменение достигнет входа регистра Б в этом же такте, пока у того не завершилось время удержания Thold. + +Для того, определить способна ли проектируемая схема работать на целевой частоте, выполняется **статический временной анализ** (**static timing analysis**, **STA**). В процессе STA, САПР рассчитывает задержки всех временных путей схемы и определяет критический путь. Итогом статического временного анализа является оценка запаса по времени (или времени простоя, англ.: slack) для каждого временного пути, когда схема работает на заданной частоте. Если slack положительный — это значит, что задержка критического пути схемы меньше предельно допустимой, и возможно увеличение частоты схемы (например, при небольшом снижении напряжения питания) в пределах данной величины. Если slack отрицательный — это значит, что задержка по критическому пути уже превысила допустимую, и для корректной работы схемы необходимо либо изменить критический путь таким образом, чтобы сократилась его задержка распространения сигнала, либо уменьшить тактовую частоту. + +К сожалению, соблюсти временны́е ограничения триггеров не всегда возможно, поскольку в некоторых случаях, вход данных может по своей природе быть асинхронен (т.е. никаким образом не зависеть от входного тактового сигнала). К примеру, данные на вход триггера подаются со входа цифровой схемы, который подключён к кнопке, нажатие на которую никак не привязано к тактовому синхроимпульсу. В других случаях, необходимо передать данные, синхронизированные с одним тактовым сигналом, в область схемы, работающей от другого тактового сигнала. Подобная ситуация называется **пересечением тактовых доменов**, или **clock domain crossing** (**CDC**). В зависимости от конкретного сценария, существуют различные схемы синхронизации, самой простой из которых является установка дополнительного триггера там, где прогнозируется возникновение метастабильного состояния (рис. 10). В высокой долей вероятности в течении 1-2 тактов на выходе синхронизирующего триггера окажется стабильное состояние, которое подавалось на вход Din. Неопределённость в количестве тактов появляется из-за того, что мы не знаем, в какую сторону "свалится" состояние первого регистра в цепи. + +![../.pic/Introduction/Sequential%20logic/fig_10.drawio.svg](../.pic/Introduction/Sequential%20logic/fig_10.drawio.svg) + +_Рисунок 10. Схема и временная диаграмма простейшего синхронизатора._ + +## Итоги главы + +1. Цифровая логика делится на **комбинационную** и **последовательностную**. + 1. **Комбинационной** называют логику без памяти, выходы которой зависят только от её входов. + 2. **последовательностной** называют логику с памятью, выходы которой зависят не только от её входов, но и от её внутреннего состояния. +2. Кроме того, цифровые схемы делят на **синхронные** и **асинхронные**. + 1. **Синхронными** называют последовательностные схемы, состояние которых меняется синхронно фронту тактового сигнала. + 2. **Асинхронными** называют как комбинационные схемы, так и последовательностные схемы, изменение состояния которых может происходить без привязки к фронту тактового сигнала. + 3. Существуют синхронные схемы с асинхронными сигналами предустановки/сброса. В большинстве случаев, они рассматриваются как обычные синхронные схемы, но их асинхронная логика предустановки/сброса, должна учитываться при расчёте **критического пути**. +3. **Критический путь** — это часть цифровой схемы с наибольшей задержкой распространения сигнала, на пути которой не встречаются элементы синхронной логики. +4. **Статическая память** — это тип памяти, который сохраняет данные в течение неопределённого времени, пока его питание остаётся включённым, без необходимости регенерации. +5. **Динамическая память** — это тип памяти, использующий для хранения конденсаторы, что приводит к необходимости в регулярном обновлении содержимого памяти для того, чтобы не утратить данные. +6. Основой статической памяти является **бистабильная ячейка**. +7. **Бистабильная ячейка** — элемент, способный сохранять одно из двух устойчивых состояний, соответствующих цифровым значениям «0» или «1». +8. Простейшей бистабильной ячейкой является петля из двух инверторов. Недостатком данной реализации является отсутствие возможности подать данные в эту ячейку извне. Этот недостаток решается преобразованием цепи в **RS-триггер**. +9. **RS-триггер** — это бистабильная ячейка, имеющая два входа: сброс (reset, R) и установка (set, S). Данная ячейка может быть построена на паре логических вентилей ИЛИ-НЕ или И-НЕ. Недостатком данной бистабильной ячейки является наличие запрещённой комбинации входных сигналов, которая может привести к непредсказуемому поведению. Данный недостаток может быть разрешён путём добавления в схему дополнительной логики, из которой получается **D-защёлка**. +10. **D-защёлка** — это бистабильная ячейка, имеющая два входа: сигнал разрешения записи (`E` / `clk`), и сигнал входных данных (D). Пока сигнал `E` активен, данные со входа `D` сохраняются в D-защёлку и идут на её выход (в таких случаях говорят, что защёлка "прозрачна"). Несмотря на то, что D-защёлка разрешает проблему запрещённого состояния RS-триггера, всё то время, пока она "прозрачна", она пропускает через себя все переходные процессы входных сигналов. +11. **D-триггер** — это бистабильная ячейка, которая подобно D-защёлке имеет входы `clk` и `D`, но который сохраняет данные только в момент одного из фронтов тактового синхроимпульса (положительного или отрицательного фронта). Как и любые бистабильные ячейки, D-триггер подвержен явлению **метастабильности**. Метастабильность в D-триггере может возникнуть, если данные на входе `D` меняются во временном окне, расположенном в окрестностях фронта тактового синхроимпульса, определяемом следующими двумя параметрами: + 1. Tsetup (**setup time**) — **время предустановки**. Это интервал, в течение которого сигнал на входе должен оставаться неизменным перед наступлением фронта тактового сигнала. + 2. Thold (**hold time**) — **время удержания**. Это интервал, в течение которого сигнал на входе должен оставаться стабильным после наступления фронта тактового сигнала. +12. **Метастабильное состояние** — это состояние бистабильной ячейки, при котором та не находится ни в одном из стабильных цифровых состояниях: `0`/`1`, находясь при этом примерно посередине между ними. Через неопределённый (но предсказуемое с точки зрения вероятностей) промежуток времени, бистабильная ячейка может выйти из этого состояния, приняв любое из значений `0`/`1`. +13. В большинстве случаев метастабильность является нежелательным явлением в цифровой схеме. Причиной такого явления может стать работа схемы на частоте, не подходящей для имеющегося у данной схемы критического пути. Для того, чтобы узнать, сможет ли схема работать на заданной частоте, проводится **статический временной анализ** (**static timing analysis**, **STA**). +14. Метастабильность может возникнуть и в случае, если сигнал данных по своей природе является асинхронным тактовому сигналу бистабильной ячейки: он может передаваться по событиям из внешнего мира, или с выхода бистабильных ячеек, работающих от других тактовых синхроимпульсов (подобная ситуация называется **пересечением тактовых доменов**, **clock domain crossing**, **CDC**). + +## Список источников + +1. Xilinx / [The Programmable Gate Array Data Book](https://archive.org/details/programmablegate00xili); +2. J. Wakerly, Digital Design: Principles and Practices (5th Edition). Pearson, 2017; +3. [Метастабильность триггера и межтактовая синхронизация](https://habr.com/ru/articles/254869/). diff --git a/Introduction/What is HDL.md b/Introduction/What is HDL.md index d9dfdcaf..0fe83fbd 100644 --- a/Introduction/What is HDL.md +++ b/Introduction/What is HDL.md @@ -1,10 +1,10 @@ # Что такое язык описания аппаратуры (HDL) -На заре появления цифровой электроники, цифровые схемы1 в виде диаграммы на бумаге были маленькими, а их реализация в виде физической аппаратуры — большой. В процессе развития электроники (и её преобразования в микроэлектронику) цифровые схемы на бумаге становились всё больше, а относительный размер их реализации в виде физических микросхем — всё меньше. На _рис. 1_, вы можете увидеть цифровую схему устройства intel 4004, выпущенного в 1971 году. +На заре появления цифровой электроники, цифровые схемы1 в виде диаграммы на бумаге были маленькими, а их реализация в виде физической аппаратуры — большой. В процессе развития электроники (и её преобразования в микроэлектронику) цифровые схемы на бумаге становились всё больше, а относительный размер их реализации в виде физических микросхем — всё меньше. На _рис. 1_, вы можете увидеть цифровую схему устройства Intel 4004, выпущенного в 1971 году. ![../.pic/Introduction/What%20is%20HDL/i4004.gif](../.pic/Introduction/What%20is%20HDL/i4004.gif) -_Рисунок 1. Цифровая схема процессора intel 4004 на уровне транзисторов[[1]](https://www.4004.com/mcs4-masks-schematics-sim.html)._ +_Рисунок 1. Цифровая схема процессора Intel 4004 на уровне транзисторов[[1]](https://www.4004.com/mcs4-masks-schematics-sim.html)._ Данная микросхема состоит из 2300 транзисторов[[2]](https://en.wikipedia.org/wiki/Intel_4004). @@ -18,11 +18,11 @@ _Рисунок 2. Масштаб размеров, которых могли б Разумеется, разрабатывая устройство, не обязательно вырисовывать на схеме каждый транзистор — можно управлять сложностью, переходя с одного уровня абстракции на другой. Например, начинать разработку схемы с уровня функциональных блоков, а затем рисовать схему для каждого отдельного блока. -К примеру, схему intel 4004 можно представить в следующем виде: +К примеру, схему Intel 4004 можно представить в следующем виде: ../.pic/Introduction/What%20is%20HDL/4004_arch.png -_Рисунок 3. Цифровая схема процессора intel 4004 на уровне функциональных блоков[[2]](https://en.wikipedia.org/wiki/Intel_4004)._ +_Рисунок 3. Цифровая схема процессора Intel 4004 на уровне функциональных блоков[[2]](https://en.wikipedia.org/wiki/Intel_4004)._ Однако несмотря на это, даже отдельные блоки порой бывают довольно сложны. Возьмем блок аппаратного шифрования по алгоритму AES[[3]](https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf) на рисунке 4: @@ -45,7 +45,7 @@ _Рисунок 5. Цифровая схема полусумматора на Текст выше и является тем описанием, по которому можно воссоздать эту цифровую схему. Если стандартизировать описание схемы, то в нем можно будет оставить только слова, выделенные жирным и курсивом. Пример того, как можно было бы описать эту схему по стандарту IEEE 1364-2005 (язык описания аппаратуры (Hardware Description Language — HDL) Verilog): -``` Verilog +```Verilog module half_sum( // устройство полусумматор cо input a, // входом a, input b, // входом b, @@ -74,12 +74,12 @@ endmodule Занятный факт: ранее было высказано предположение о том, что инженеры перестали разрабатывать устройства, рисуя цифровые схемы в промежуток времени между 1971-ым и 2022-ым годами. Так вот, первая конференция, посвященная языкам описания аппаратуры состоялась в 1973-ем году[[4, стр. 8]](https://dl.acm.org/doi/pdf/10.1145/3386337). Таким образом, Intel 4004 можно считать одним из последних цифровых устройств, разработанных без использования языков описания аппаратуры. -## Источники +## Список источников 1. [Intel 4004 — 50th Anniversary Project](https://www.4004.com/mcs4-masks-schematics-sim.html); 2. [Страница википедии по Intel 4004](https://en.wikipedia.org/wiki/Intel_4004); 3. [F.Ka˘gan. Gürkaynak / Side Channel Attack Secure Cryptographic Accelerators](https://iis-people.ee.ethz.ch/~kgf/acacia/acacia_thesis.pdf); -4. [4. FIPS 197, Advanced Encryption Standart (AES)](https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf); +4. [FIPS 197, Advanced Encryption Standart (AES)](https://csrc.nist.gov/files/pubs/fips/197/final/docs/fips-197.pdf); 5. [P. Flake, P. Moorby, S. Golson, A. Salz, S. Davidmann / Verilog HDL and Its Ancestors and Descendants](https://dl.acm.org/doi/pdf/10.1145/3386337). ## Примечания diff --git a/Labs/01. Adder/README.md b/Labs/01. Adder/README.md index 952eb6a0..00e489a5 100644 --- a/Labs/01. Adder/README.md +++ b/Labs/01. Adder/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 1 "Сумматор" +# Лабораторная работа №1 "Сумматор" ## Цель @@ -21,7 +21,7 @@ Итогом лабораторной работы будет создание устройства, способного складывать два числа. Но перед тем, как учиться создавать подобное устройство, необходимо немного освоиться в самом процессе складывания чисел. -Давайте начнем с примера и сложим в столбик произвольную пару чисел, например 42 и 79: +Давайте начнём с примера и сложим в столбик произвольную пару чисел, например 42 и 79: ![../../.pic/Labs/lab_01_adder/column_add_dec.drawio.svg](../../.pic/Labs/lab_01_adder/column_add_dec.drawio.svg) @@ -63,13 +63,13 @@ _Таблица истинности одноразрядного сложения._ -`S` — это младший разряд суммы, записываемый в столбце сложения под слагаемыми `a` и `b`. `C` (_carry_, перенос) — это старший разряд суммы, записываемый левее, если произошел перенос разряда. Как мы видим, перенос разряда происходит только в случае, когда оба числа одновременно равны единице. При этом значение `S` обращается в `0`, и результат записывается как `10`, что в двоичной системе означает `2`. Кроме того, значение `S` равно `0` и в случае, когда оба операнда одновременно равны нулю. Вы можете заметить, что `S` равно нулю в тех случаях, когда `а` и `b` равны, и не равно нулю в противоположном случае. Подобным свойством обладает логическая операция **Исключающее ИЛИ** (**eXclusive OR**, **XOR**): +`S` — это младший разряд 2-битного результата суммы, записываемый в столбце сложения под слагаемыми `a` и `b`. `C` (_carry_, перенос) — это старший разряд суммы, записываемый левее, если произошёл перенос разряда. Как мы видим, перенос разряда происходит только в случае, когда оба числа одновременно равны единице. При этом значение `S` обращается в `0`, и результат записывается как `10`, что в двоичной системе означает `2`. Кроме того, значение `S` равно `0` и в случае, когда оба операнда одновременно равны нулю. Вы можете заметить, что `S` равно нулю в тех случаях, когда `а` и `b` равны, и не равно нулю в противоположном случае. Подобным свойством обладает логическая операция **Исключающее ИЛИ** (**eXclusive OR**, **XOR**): ![../../.pic/Labs/lab_01_adder/tt2.png](../../.pic/Labs/lab_01_adder/tt2.png) _Таблица истинности операции Исключающее ИЛИ (XOR)._ -Для бита переноса всё ещё проще — он описывается операцией логическое И: +Для бита переноса всё ещё проще — он описывается операцией **логическое И**: ![../../.pic/Labs/lab_01_adder/tt3.png](../../.pic/Labs/lab_01_adder/tt3.png) @@ -81,7 +81,7 @@ _Таблица истинности операции И._ _Рисунок 1. Цифровая схема устройства, складывающего два операнда с сохранением переноса (полусумматора)._ -Однако, в описании полного 1-битного сумматора сказано, что у него есть три входа, а в наших таблицах истинности и на схеме выше их только два (схема, представленная на рис. 1, реализует так называемый "полусумматор"). На самом деле, на каждом этапе сложения в столбик мы всегда складывали три числа: цифру верхнего числа, цифру нижнего числа, и единицу в случае переноса разряда из предыдущего столбца (если с предыдущего разряда не было переноса, прибавление нуля неявно опускалось). +Однако, в описании полного 1-битного сумматора сказано, что у него есть три входа, а в наших таблицах истинности и на схеме выше их только два (схема, представленная на _рис. 1_, реализует так называемый "полусумматор"). На самом деле, на каждом этапе сложения в столбик мы всегда складывали три числа: цифру верхнего числа, цифру нижнего числа, и единицу в случае переноса разряда из предыдущего столбца (если с предыдущего разряда не было переноса, прибавление нуля неявно опускалось). Таким образом, таблица истинности немного усложняется: @@ -107,50 +107,25 @@ _Рисунок 2. Цифровая схема полного 1-битного ## Практика -Реализуем схему полусумматора (рис.1) в виде модуля, описанного на языке SystemVerilog. +Реализуем схему полусумматора (_рис. 1_) в виде модуля, описанного на языке SystemVerilog. Модуль `half_adder` имеет два входных сигнала и два выходных. Входы `a_i` и `b_i` идут на два логических элемента: Исключающее ИЛИ и И, выходы которых подключены к выходам модуля `sum_o` и `carry_o` соответственно. -
- **Прочти меня перед использованием кода из примера.** -### Во все примеры кода намеренно вставлены неподдерживаемые символы. Не копируй, одумайся! - - Важной частью изучения языка является практика по написанию кода. Даже если перепечатывая пример, вы не до конца его понимаете, вы запоминаете структуру кода и его конструкции. Вы изучаете этот пример для себя, а не для оценки, так что будьте честны с собой и воспроизведите пример самостоятельно. - -
- - — Но мне очень надо. - - ![../../.pic/Labs/lab_01_adder/im_watching_you.jpg](../../.pic/Labs/lab_01_adder/im_watching_you.jpg) - -
- -
- - — Я переписал пример точь-в-точь, а он все равно не работает! - - Позови преподавателя, он тебе поможет. - -
- -
- -```systemverilog +```Verilog module half_adder( - inрut logic a_i, // Входные сигналы - inрut logic b_i, + input logic a_i, // Входные сигналы + input logic b_i, - outрut logic sum_o, // Выходной сигнал - outрut logic carry_o - - ); + output logic sum_o, // Выходные сигналы + output logic carry_o +); - assign sum_o = a_i ^ b_i; - assign carry_o = a_i & b_i; +assign sum_o = a_i ^ b_i; +assign carry_o = a_i & b_i; - endmodule +endmodule ``` _Листинг 1. SystemVerilog-код модуля half_adder._ @@ -161,19 +136,19 @@ _Листинг 1. SystemVerilog-код модуля half_adder._ _Рисунок 3. Цифровая схема модуля half_adder, сгенерированная САПР Vivado._ -Схема похожа на _рис. 1_, но как проверить, что эта схема не содержит ошибок и делает именно то, что от нее ожидается? +Схема похожа на _рис. 1_, но как проверить, что эта схема не содержит ошибок и делает именно то, что от неё ожидается? Для этого необходимо провести моделирование этой схемы. Во время моделирования на входы подаются тестовые воздействия. Каждое изменение входных сигналов приводит к каскадному изменению состояний внутренних цепей, что в свою очередь приводит к изменению значений на выходных сигналах схемы. -Подаваемые на схему входные воздействия формируются верификационным окружением. Верификационное окружение (в дальнейшем будет использован термин "**тестбенч**") — это особый несинтезируемый модуль, который не имеет входных или выходных сигналов. Ему не нужны входные сигналы, поскольку он сам является генератором всех своих внутренних сигналов, и ему не нужны выходные сигналы, поскольку этот модуль ничего не вычисляет, только подает входные воздействия на проверяемый модуль. +Подаваемые на схему входные воздействия формируются верификационным окружением. Верификационное окружение (в дальнейшем будет использован термин "**тестбенч**") — это особый несинтезируемый модуль, который не имеет входных или выходных сигналов. Ему не нужны входные сигналы, поскольку он сам является генератором всех своих внутренних сигналов, и ему не нужны выходные сигналы, поскольку этот модуль ничего не вычисляет, только подаёт входные воздействия на проверяемый модуль. Внутри тестбенча можно использовать конструкции из несинтезируемого подмножества языка SystemVerilog, в частности программный блок `initial`, в котором команды выполняются последовательно, что делает этот блок чем-то отдаленно похожим на проверяющую программу. Поскольку изменение внутренних цепей происходит с некоторой задержкой относительно изменений входных сигналов, при моделировании есть возможность делать паузы между командами. Это делается с помощью специального символа #, за которым указывается количество времени симуляции, которое нужно пропустить перед следующей командой. Перед тем как писать верификационное окружение, необходимо составить план того, как будет проводиться проверка устройства (составить верификационный план). Ввиду предельной простоты устройства, план будет состоять из одного предложения: -> Поскольку устройство не имеет внутреннего состояния, которое могло бы повлиять на результат, а число всех его возможных входных наборов воздействий равно четырем, мы можем проверить его работу, перебрав все возможные комбинации его входных сигналов. +> Поскольку устройство не имеет внутреннего состояния, которое могло бы повлиять на результат, а число всех его возможных входных наборов воздействий равно четырём, мы можем проверить его работу, перебрав все возможные комбинации его входных сигналов. -```SystemVerilog +```Verilog module testbench(); // <- Не имеет ни входов, ни выходов! logic a, b, carry, sum; @@ -224,13 +199,13 @@ _Рисунок 5. Схема 4-битного сумматора._ _Рисунок 6. Схема 4-битного сумматора, сгенерированная САПР Vivado._ -Несмотря на запутанность схемы, если присмотреться, вы увидите, как от шин A, B и S отходят линии к каждому из сумматоров, а бит переноса передается от предыдущего сумматора к следующему. +Несмотря на запутанность схемы, если присмотреться, вы увидите, как от шин A, B и S отходят линии к каждому из сумматоров, а бит переноса передаётся от предыдущего сумматора к следующему. ## Задание Опишите полный 1-битный сумматор, схема которого представлена на _[Рис. 2](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)_. Прототип модуля следующий: -```SystemVerilog +```Verilog module fulladder( input logic a_i, input logic b_i, @@ -242,13 +217,13 @@ module fulladder( Далее, вам необходимо реализовать полный 32-битный сумматор со следующим прототипом: -```systemverilog +```verilog module fulladder32( - іnput logic [31:0] a_i, - іnput logic [31:0] b_i, - іnput logic carry_i, - оutput logic [31:0] sum_o, - оutput logic carry_o + input logic [31:0] a_i, + input logic [31:0] b_i, + input logic carry_i, + output logic [31:0] sum_o, + output logic carry_o ); ``` @@ -256,7 +231,7 @@ module fulladder32( Если вы решите делать 4-битный сумматор, то модуль должен быть описан в соответствии со следующим прототипом: -```SystemVerilog +```Verilog module fulladder4( input logic [3:0] a_i, input logic [3:0] b_i, @@ -271,12 +246,12 @@ module fulladder4( Создание массива модулей схоже с созданием одного модуля за исключением того, что после имени сущности модуля указывается диапазон, определяющий количество модулей в массиве. При этом подключение сигналов к массиву модулей осуществляется следующим образом: - если разрядность подключаемого сигнала совпадает с разрядностью порта модуля из массива, этот сигнал подключается к каждому из модулей в массиве; -- если разрядность подключаемого сигнала превосходит разрядность порта модуля из массива в `N` раз (где `N` — количество модулей в массиве), к модулю подключается соответствующий диапазон бит подключаемого сигнала (диапазон младших бит будет подключен к модулю с меньшим индексом в массиве). +- если разрядность подключаемого сигнала превосходит разрядность порта модуля из массива в `N` раз (где `N` — количество модулей в массиве), к модулю подключается соответствующий диапазон бит подключаемого сигнала (диапазон младших бит будет подключён к модулю с меньшим индексом в массиве). - если разрядность подключаемого сигнала не подходит ни под один из описанных выше пунктов, происходит ошибка синтеза схемы, поскольку в этом случае САПР не способен понять каким образом подключать данный сигнал к каждому модулю из массива. -Далее идет пример того, как можно создать массив модулей: +Далее идёт пример того, как можно создать массив модулей: -```SystemVerilog +```Verilog module example1( input logic [3:0] a, input logic b, @@ -303,11 +278,11 @@ example1 instance_array[7:0]( // Создается массив из 8 моду // A[3:0], к instance_array[7] будет подключен // диапазон A[31:28]). - .b(B) // Поскольку разрядность сигнала B совпадает с + .b(B), // Поскольку разрядность сигнала B совпадает с // разрядностью входа b, сигнал B будет подключен // как есть ко всем модулям в массиве. - .c(C[7:0]) // Поскольку разрядность сигнала C не равна + .c(C[7:0]), // Поскольку разрядность сигнала C не равна // ни разрядности входа c, ни его увосьмиренной // разрядности, мы должны выбрать такой диапазон // бит, который будет удовлетворять одному из @@ -322,36 +297,18 @@ _Листинг 3. Пример создания массива модулей._ ### Порядок выполнения задания -1. Создайте проект, согласно [руководству по созданию проекта в Vivado](../../Vivado%20Basics/Vivado%20trainer.md) -2. В `Design Sources` проекта создайте `SystemVerilog`-файл `fulladder`. -3. Опишите в файле модуль `fulladder`, схема которого представлена на _[Рис. 2](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)_. -4. Проверьте 1-битный сумматор. Для этого: - 1. В `Simulation Sources` проекта создайте `SystemVerilog`-файл `tb_fulladder`. - 2. Вставьте содержимое файла [`tb_fulladder.sv`](tb_fulladder.sv), расположенного рядом с данным документом. - 3. Запустите моделирование. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 4. Убедитесь по сигналам временной диаграммы, что модуль работает корректно. -5. В `Design Sources` проекта создайте `SystemVerilog`-файл `fulladder4`. -6. Опишите модуль `fulladder4`, схема которого представлена на _Рис. 5 и 6_, используя [`иерархию модулей`](../../Basic%20Verilog%20structures/Modules.md#%D0%B8%D0%B5%D1%80%D0%B0%D1%80%D1%85%D0%B8%D1%8F-%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D0%B5%D0%B9), чтобы в нем выполнялось поразрядное сложение двух 4-битных чисел и входного бита переноса. Некоторые входы и выходы модуля будет необходимо описать в виде [`векторов`](../../Basic%20Verilog%20structures/Modules.md#векторы). - 1. Обратите внимание, что входной бит переноса должен подаваться на сумматор, выполняющий сложение нулевого разряда, выходной бит переноса соединяется с выходным битом переноса сумматора, выполняющего сложение 4-го разряда. -7. Проверьте 4-битный сумматор. Для этого: - 1. В `Simulation Sources` проекта создайте `SystemVerilog`-файл `tb_fulladder4`. - 2. Вставьте содержимое файла [`tb_fulladder4.sv`](tb_fulladder4.sv). Нажмите по нему в окне `Sources` ПКМ и выберите `Set as Top`. - 3. Запустите моделирование. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 4. Проверьте содержимое TCL-консоли. Убедитесь в появлении сообщения о завершении теста. - 5. Убедитесь по сигналам временной диаграммы, что модуль работает корректно. -8. В `Design Sources` проекта создайте `SystemVerilog`-файл `fulladder32`. -9. Опишите модуль `fulladder32` так, чтобы в нем выполнялось поразрядное сложение двух 32-битных чисел и входного бита переноса. Его можно реализовать через последовательное соединение восьми 4-битных сумматоров, либо же можно соединить 32 1-битных сумматора (как вручную, так и с помощью создания массива модулей). - 1. Обратите внимание, что входной бит переноса должен подаваться на сумматор, выполняющий сложение нулевого разряда, выходной бит переноса соединяется с выходным битом переноса сумматора, выполняющего сложение 31-го разряда. -10. Проверьте 32-битный сумматор. Для этого: - 1. В `Simulation Sources` проекта создайте `SystemVerilog`-файл `tb_fulladder32`. - 2. Вставьте содержимое файла [`tb_fulladder32.sv`](tb_fulladder32.sv). Нажмите по нему в окне `Sources` ПКМ и выберите `Set as Top`. - 3. Запустите моделирование. - 4. Проверьте содержимое TCL-консоли. Убедитесь в появлении сообщения о завершении теста. - 5. Если в tcl-консоли были сообщения об ошибках, разберитесь в причине ошибок по временной диаграмме и [исправьте их](../../Vivado%20Basics/Debug%20manual.md). -11. Проверьте работоспособность вашей цифровой схемы в ПЛИС. Для этого: - 1. Добавьте файлы из папки [`board files`](https://github.com/MPSU/APS/tree/master/Labs/01.%20Adder/board%20files) в проект. - 1. Файл [nexys_adder.sv](https://github.com/MPSU/APS/tree/master/Labs/01.%20Adder/board%20files/nexys_adder.sv) необходимо добавить в `Design Sources` проекта. - 2. Файл [nexys_a7_100t.xdc](https://github.com/MPSU/APS/tree/master/Labs/01.%20Adder/board%20files/nexys_a7_100t.xdc) необходимо добавить в `Constraints` проекта. - 2. Выберите `nexys_adder` в качестве модуля верхнего уровня (`top-level`) в `Design Sources`. - 3. Выполните генерацию битстрима и сконфигурируйте ПЛИС. Для этого воспользуйтесь [следующей инструкцией](../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). - 4. Описание логики работы модуля верхнего уровня и связи периферии ПЛИС с реализованным модулем находится в папке [`board files`](https://github.com/MPSU/APS/tree/master/Labs/01.%20Adder/board%20files). +1. Создайте проект, согласно [руководству по созданию проекта в Vivado](../../Vivado%20Basics/01.%20New%20project.md) +2. Опишите модуль `fulladder`, схема которого представлена на _[Рис. 2](../../.pic/Labs/lab_01_adder/fig_02.drawio.svg)_. + 1. Модуль необходимо описать с таким же именем и портам, как указано в задании. +3. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_01.tb_fulladder.sv`](lab_01.tb_fulladder.sv). Убедитесь по сигналам временной диаграммы, что модуль работает корректно. В случае обнаружения некорректного поведения сигналов суммы и выходного бита переноса, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) причину этого поведения, и устранить её. +4. Опишите модуль `fulladder4`, схема которого представлена на _Рис. 5 и 6_, используя [`иерархию модулей`](../../Basic%20Verilog%20structures/Modules.md#%D0%B8%D0%B5%D1%80%D0%B0%D1%80%D1%85%D0%B8%D1%8F-%D0%BC%D0%BE%D0%B4%D1%83%D0%BB%D0%B5%D0%B9), чтобы в нем выполнялось поразрядное сложение двух 4-битных чисел и входного бита переноса. Некоторые входы и выходы модуля будет необходимо описать в виде [`векторов`](../../Basic%20Verilog%20structures/Modules.md#векторы). + 1. Модуль необходимо описать с таким же именем и портам, как указано в задании. + 2. Обратите внимание, что входной бит переноса должен подаваться на сумматор, выполняющий сложение нулевого разряда, выходной бит переноса соединяется с выходным битом переноса сумматора, выполняющего сложение 4-го разряда. +5. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_01.tb_fulladder4.sv`](lab_01.tb_fulladder4.sv). Убедитесь по сигналам временной диаграммы, что модуль работает корректно. В случае обнаружения некорректного поведения сигналов суммы и выходного бита переноса, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) причину этого поведения, и устранить её. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +6. Опишите модуль `fulladder32` так, чтобы в нем выполнялось поразрядное сложение двух 32-битных чисел и входного бита переноса. Его можно реализовать через последовательное соединение восьми 4-битных сумматоров, либо же можно соединить 32 1-битных сумматора (как вручную, так и с помощью создания массива модулей). + 1. Модуль необходимо описать с таким же именем и портам, как указано в задании. + 2. Обратите внимание, что входной бит переноса должен подаваться на сумматор, выполняющий сложение нулевого разряда, выходной бит переноса соединяется с выходным битом переноса сумматора, выполняющего сложение 31-го разряда. +7. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_01.tb_fulladder32.sv`](lab_01.tb_fulladder32.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +8. Проверьте работоспособность вашей цифровой схемы в ПЛИС. diff --git a/Labs/01. Adder/board files/README.md b/Labs/01. Adder/board files/README.md index 1737b544..c970038a 100644 --- a/Labs/01. Adder/board files/README.md +++ b/Labs/01. Adder/board files/README.md @@ -2,7 +2,7 @@ После того, как вы создали проверили на моделировании 32-разрядный сумматор, вам необходимо проверить его работу на прототипе в ПЛИС. -Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). +Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/07.%20Program%20and%20debug.md). На _рис. 1_ представлена схема прототипа в ПЛИС. diff --git a/Labs/01. Adder/tb_fulladder.sv b/Labs/01. Adder/lab_01.tb_fulladder.sv similarity index 97% rename from Labs/01. Adder/tb_fulladder.sv rename to Labs/01. Adder/lab_01.tb_fulladder.sv index 5692d6e2..a4ec5571 100644 --- a/Labs/01. Adder/tb_fulladder.sv +++ b/Labs/01. Adder/lab_01.tb_fulladder.sv @@ -9,7 +9,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_fulladder(); +module lab_01_tb_fulladder(); logic tb_a_i; logic tb_b_i; diff --git a/Labs/01. Adder/tb_fulladder32.sv b/Labs/01. Adder/lab_01.tb_fulladder32.sv similarity index 94% rename from Labs/01. Adder/tb_fulladder32.sv rename to Labs/01. Adder/lab_01.tb_fulladder32.sv index 1c7fb9c6..c1e77681 100644 --- a/Labs/01. Adder/tb_fulladder32.sv +++ b/Labs/01. Adder/lab_01.tb_fulladder32.sv @@ -9,7 +9,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_fulladder32(); +module lab_01_tb_fulladder32(); logic [31:0] tb_a_i; logic [31:0] tb_b_i; @@ -32,7 +32,6 @@ module tb_fulladder32(); initial begin $display("Test has been started"); - $display( "\n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop(); sequential_add_test(); random_test(); $display("\nTest has been finished\nNumber of errors: %d\n", err_cnt); diff --git a/Labs/01. Adder/tb_fulladder4.sv b/Labs/01. Adder/lab_01.tb_fulladder4.sv similarity index 89% rename from Labs/01. Adder/tb_fulladder4.sv rename to Labs/01. Adder/lab_01.tb_fulladder4.sv index 7116ee47..f830f5df 100644 --- a/Labs/01. Adder/tb_fulladder4.sv +++ b/Labs/01. Adder/lab_01.tb_fulladder4.sv @@ -9,7 +9,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_fulladder4(); +module lab_01_tb_fulladder4(); logic [3:0] tb_a_i; logic [3:0] tb_b_i; @@ -30,7 +30,6 @@ module tb_fulladder4(); initial begin $display("Test has been started"); - $display( "\n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop(); #5ns; test_case = 9'd0; repeat(512) begin diff --git a/Labs/02. Arithmetic-logic unit/README.md b/Labs/02. Arithmetic-logic unit/README.md index f69056d9..6c877adb 100644 --- a/Labs/02. Arithmetic-logic unit/README.md +++ b/Labs/02. Arithmetic-logic unit/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 2. Арифметико-логическое устройство +# Лабораторная работа №2. Арифметико-логическое устройство Так как основной задачей процессора является обработка цифровых данных, одним из его основных блоков является арифметико-логическое устройство (АЛУ). Задача АЛУ производить над входными данным арифметические и поразрядно логические операции. @@ -8,7 +8,9 @@ ## Материалы для подготовки к лабораторной работе -Освоить [описание мультиплексора на языке SystemVerilog](../../Basic%20Verilog%20structures/Multiplexors.md). +В дополнение к [материалам](../../Basic%20Verilog%20structures/), изученным в ходе предыдущей лабораторной работы, вам рекомендуется ознакомиться с: + +- способами описания [мультиплексора](../../Basic%20Verilog%20structures/Multiplexors.md) на языке SystemVerilog. ## Общий ход выполнения работы @@ -20,7 +22,7 @@ ## Теория -Арифметико-логическое устройство (АЛУ, Arithmetic Logic Unit – ALU) – это блок процессора, выполняющий арифметические и поразрядно логические операции. Разница между арифметическими и логическими операциями в отсутствии у последних бита переноса, так как логические операции происходят между 1-битными числами и дают 1-битный результат, а в случае АЛУ (в рамках данной лабораторной работы) одновременно между 32-мя 1-битными парами чисел. В логических операциях результаты значений отдельных битов друг с другом никак не связаны. +**Арифметико-логическое устройство** (**АЛУ**, Arithmetic Logic Unit – ALU) – это блок процессора, выполняющий арифметические и поразрядно логические операции. Разница между арифметическими и логическими операциями в отсутствии у последних бита переноса, так как логические операции происходят между 1-битными числами и дают 1-битный результат, а в случае АЛУ (в рамках данной лабораторной работы) одновременно между 32-мя 1-битными парами чисел. В логических операциях результаты значений отдельных битов друг с другом никак не связаны. Также, кроме результата операций, АЛУ формирует флаги, которые показывают выполняется ли заданное условие. Например, выведет `1`, если один операнд меньше другого. @@ -32,7 +34,7 @@ _Рисунок 1. Структурное обозначение элемент На рис. 1 изображен пример АЛУ, используемый в книге "Цифровая схемотехника и архитектура компьютера" Харрис и Харрис. На входы `A` и `B` поступают операнды с разрядностью _N_. На 3-битный вход `F` подается код операции. Например, если туда подать `000`, то на выходе `Y` появится результат операции _логическое И_ между битами операндов `A` и `B`. Если на `F` подать `010`, то на выходе появится результат сложения. Это лишь пример, разрядность и коды могут отличаться в зависимости от количества выполняемых операций и архитектуры. -Существует несколько подходов к реализации АЛУ, отличающиеся внутренней организацией. В лабораторных работах применяется повсеместно используемый подход мультиплексирования операций, то есть подключения нескольких операционных устройств (которые выполняют какие-то операции, например сложения, логического И и т.п.) к мультиплексору, который будет передавать результат нужного операционного устройства на выходы АЛУ. +Существует несколько подходов к реализации АЛУ, отличающиеся внутренней организацией. В лабораторных работах применяется повсеместно используемый подход мультиплексирования операций, то есть подключения нескольких операционных устройств (которые выполняют какие-то операции, например сложения, логического ИЛИ и т.п.) к мультиплексору, который будет передавать результат нужного операционного устройства на выходы АЛУ. Рассмотрим данный подход на примере все того же АЛУ MIPS из книги Харрисов. На рис. 2, в левой его части, изображена внутренняя организация этого АЛУ, справа – таблица соответствия кодов операциям. На выходе схемы (внизу) стоит 4-входовой мультиплексор, управляемый двумя из трех битов `F`. К его входам подключены _N_ логических И (побитовое И _N_-битных операндов), _N_ логических ИЛИ, _N_-битный сумматор и Zero Extend – устройство, дополняющее слева нулями 1-битное число до N-битного. @@ -75,40 +77,43 @@ _Рисунок 3. Пример исполнения операции АЛУ._ Пример: -```SystemVerilog +```Verilog module overflow #(parameter WIDTH = 32)( input logic [WIDTH-1 : 0] a, b, - output logic overflow + output logic overflow ); -logic [WIDТН : 0] sum; +logic [WIDTH : 0] sum; -ass𝚒gn sum = a + b; -ass𝚒gn overflow = sum[WIDTH]; +assign sum = a + b; +assign overflow = sum[WIDTH]; endmodule ``` +_Листинг 1. Пример описания параметра в прототипе модуля._ + В случае, если параметр не влияет на разрядность портов, его можно объявить в теле модуля: -```SystemVerilog +```Verilog module toaster( input logic [31:0] command, output logic power ) parameter TOASTER_EN = 32'haf3c5bd0; - assign power = command == TOASTER_EN; endmodule ``` +_Листинг 2. Пример описания параметра в теле модуля._ + В случае АЛУ будет удобно использовать параметры для обозначения кодов команд. Во-первых, для того чтобы в `case` не допустить ошибок, а во-вторых – чтобы можно было легко менять управляющие коды для повторного использования АЛУ в других проектах. -Сравните сами: +Сравните сами _листинги 3 и 4_: -```SystemVerilog +```Verilog //parameter SLT = 5'b00011; //parameter BEQ = 5'b11000; @@ -121,28 +126,30 @@ always_comb 5'b11000: //... // никуда не годится ``` -и +_Листинг 3. Пример описания модуля, использующего "магические" числа._ -```SystemVerilog +```Verilog parameter SLT = 5'b00011; parameter BEQ = 5'b11000; //... -аlwауs_comb +always_comb case(ALUOp) //... SLT: //... // очень понятно BEQ: //... // так лаконично и красиво ``` -С параметрами гораздо взрослее, серьезнее и понятнее смотрится. Кстати, сразу на заметку: в SystemVerilog можно объединять группу параметров в **пакет** (package), а затем импортировать его внутрь модуля, позволяя переиспользовать параметры без повторного их прописывания для других модулей. +_Листинг 4. Пример описания модуля, использующего параметры._ + +С параметрами смотрится гораздо взрослее, серьёзнее и понятнее. Кстати, сразу на заметку: в SystemVerilog можно объединять группу параметров в **пакет** (package), а затем импортировать его внутрь модуля, позволяя переиспользовать параметры без повторного их прописывания для других модулей. Делается это следующим образом. Сперва создается SystemVerilog-файл, который будет содержать пакет (к примеру, содержимое файла может быть таким): -```SystemVerilog +```Verilog package riscv_params_pkg; parameter ISA_WIDTH = 32; parameter ANOTHER_EX = 15; @@ -151,16 +158,17 @@ endpackage Далее, внутри модуля, которому нужны параметры из этого пакета, необходимо сделать соответствующий импорт этих параметров. Это можно сделать либо для каждого параметра отдельно, либо импортировать все параметры сразу: -```SystemVerilog -module riscv_processor( +```Verilog +module riscv_processor +//import riscv_params_pkg::*; +import riscv_params_pkg::ISA_WIDTH; // Если необходимо импортировать +( //...Порты ); -import riscv_params_pkg::ISA_WIDTH; // Если необходимо импортировать -import riscv_params_pkg::ANOTHER_EX; // все параметры в пакете,эти две строчки +import riscv_params_pkg::ANOTHER_EX; // все параметры в пакете, эти две строчки // могут быть заменены закомментированной - // ниже строкой: -//import riscv_params_pkg::*; + // выше строкой: endmodule ``` @@ -191,14 +199,14 @@ endmodule Необходимо на языке SystemVerilog реализовать АЛУ в соответствии со следующим прототипом: -```SystemVerilog +```Verilog -module аlu_r𝚒sсv ( - 𝚒nput logic [31:0] a_i, - 𝚒nput logic [31:0] b_i, - 𝚒nput logic [4:0] alu_op_i, - оutput logic flag_o, - оutput logic [31:0] result_o +module alu ( + input logic [31:0] a_i, + input logic [31:0] b_i, + input logic [4:0] alu_op_i, + output logic flag_o, + output logic [31:0] result_o ); import alu_opcodes_pkg::*; // импорт параметров, содержащих @@ -271,8 +279,8 @@ _Таблица 2. Список операций сравнения._ Конструкция `$signed` говорит САПР интерпретировать число, переданное в качестве операнда, как знаковое. -```SystemVerilog - аss𝚒gn Rеsult = $s𝚒gnеd(А) >>> В[4:0]; +```Verilog +assign Result = $signed(A) >>> B[4:0]; ``` В этом примере некоторому сигналу `Result` присваивают результат сдвига знакового числа `A` на значение количества бит получаемых из младших 5 бит сигнала `B`. @@ -285,9 +293,8 @@ _Рисунок 4. Пример схемы, реализующей АЛУ._ ### Порядок выполнения задания -1. Добавьте в проект файл [`alu_opcodes_pkg.sv`](alu_opcodes_pkg.sv). Этот файл содержит объявление пакета `alu_opcodes_pkg`, в котором прописаны все опкоды АЛУ. -2. В `Design Sources` проекта создайте `SystemVerilog`-файл `аlu_r𝚒sсv.sv`. -3. Опишите в нем модуль АЛУ с таким же именем и портами, как указано в [задании](#задание). +1. Добавьте в `Design Sources` проекта файл [`alu_opcodes_pkg.sv`](alu_opcodes_pkg.sv). Этот файл содержит объявление пакета `alu_opcodes_pkg`, в котором прописаны все опкоды АЛУ. +2. Опишите модуль `alu` с таким же именем и портами, как указано в [задании](#задание). 1. Поскольку у вас два выходных сигнала, зависящих от сигнала `alu_op_i`, вам потребуется описать два разных [мультиплексора](../../Basic%20Verilog%20structures/Multiplexors.md) (их лучше всего описывать через два отдельных блока `case`). При описании, используйте `default` на оставшиеся комбинации сигнала `alu_op_i`. 2. Следите за разрядностью ваших сигналов. 3. Для реализации АЛУ, руководствуйтесь таблицей с операциями, а не схемой в конце задания, которая приведена в качестве референса. Обратите внимание, в одной половине операций `flag_o` должен быть равен нулю, в другой `result_o` (т.е. всегда либо один, либо другой сигнал должен быть равен нулю). Именно поэтому удобней всего будет описывать АЛУ в двух разных блоках `case`. @@ -296,19 +303,9 @@ _Рисунок 4. Пример схемы, реализующей АЛУ._ 1. При подключении сумматора, на входной бит переноса необходимо подать значение `1'b0`. Если не подать значение на входной бит переноса, результат суммы будет не определен (т.к. не определено одно из слагаемых). 2. Выходной бит переноса при подключении сумматора можно не указывать, т.к. он использоваться не будет. 6. При реализации операций сдвига, руководствуйтесь [особенностями реализации сдвигов](#особенности-реализации-сдвига). -4. После реализации модуля АЛУ его нужно будет проверить с помощью тестового окружения. - 1. Добавьте файл [`tb_alu.sv`](tb_alu.sv) в `Simulation sources`. - 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран модуль `tb_alu`. - 4. Убедитесь, что симуляция завершена (об этом будет соответствующее сообщение в консоли). По завершению симуляции, в случае отсутствия ошибок, будет выведено сообщение "SUCCESS", в противном случае будут выведены сообщения об этих ошибках. - 5. В случае, если были найдены ошибки, вы должны найти и исправить их. Для этого руководствуйтесь [документом](../../Vivado%20Basics/Debug%20manual.md). -5. Проверьте работоспособность вашей цифровой схемы в ПЛИС. Для этого: - 1. Добавьте файлы из папки [`board files`](https://github.com/MPSU/APS/tree/master/Labs/02.%20Arithmetic-logic%20unit/board%20files) в проект. - 1. Файл [nexys_alu.sv](https://github.com/MPSU/APS/tree/master/Labs/02.%20Arithmetic-logic%20unit/board%20files/nexys_alu.sv) необходимо добавить в `Design Sources` проекта. - 2. Файл [nexys_a7_100t.xdc](https://github.com/MPSU/APS/tree/master/Labs/02.%20Arithmetic-logic%20unit/board%20files/nexys_a7_100t.xdc) необходимо добавить в `Constraints` проекта. В случае, если вы уже добавляли одноименный файл в рамках предыдущих лабораторных работ, его содержимое необходимо заменить содержимым нового файла. - 2. Выберите `nexys_alu` в качестве модуля верхнего уровня (`top-level`). - 3. Выполните генерацию битстрима и сконфигурируйте ПЛИС. Для этого воспользуйтесь [следующей инструкцией](../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). - 4. Описание логики работы модуля верхнего уровня и связи периферии ПЛИС с реализованным модулем находится в папке [`board files`](https://github.com/MPSU/APS/tree/master/Labs/02.%20Arithmetic-logic%20unit/board%20files). +3. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_02.tb_alu.sv`](lab_02.tb_alu.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +4. Проверьте работоспособность вашей цифровой схемы в ПЛИС. ## Список использованной литературы diff --git a/Labs/02. Arithmetic-logic unit/board files/README.md b/Labs/02. Arithmetic-logic unit/board files/README.md index 4ee9d92d..f8900337 100644 --- a/Labs/02. Arithmetic-logic unit/board files/README.md +++ b/Labs/02. Arithmetic-logic unit/board files/README.md @@ -2,7 +2,7 @@ После того, как вы проверили на моделировании АЛУ, вам необходимо проверить его работу на прототипе в ПЛИС. -Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). +Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/07.%20Program%20and%20debug.md). На _рис. 1_ представлена схема прототипа в ПЛИС. diff --git a/Labs/02. Arithmetic-logic unit/tb_alu.sv b/Labs/02. Arithmetic-logic unit/lab_02.tb_alu.sv similarity index 99% rename from Labs/02. Arithmetic-logic unit/tb_alu.sv rename to Labs/02. Arithmetic-logic unit/lab_02.tb_alu.sv index 5a7ddf02..2a2a39a4 100644 --- a/Labs/02. Arithmetic-logic unit/tb_alu.sv +++ b/Labs/02. Arithmetic-logic unit/lab_02.tb_alu.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_alu(); +module lab_02_tb_alu(); import alu_opcodes_pkg::*; @@ -28,7 +28,7 @@ wire [31:0] result_ref; wire comparison_result_ref; -alu_riscv DUT +alu DUT ( .alu_op_i (operator_i ), .a_i (operand_a_i), @@ -38,7 +38,7 @@ alu_riscv DUT .flag_o (comparison_result_dut) ); -alu_riscv_ref REF +alu_ref REF ( .alu_op_i (operator_i ), .a_i (operand_a_i), @@ -54,7 +54,6 @@ reg [8*9:1] operator_type; initial begin $display("Test has been started"); - $display( "\n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop(); //X_test(); result_test(); flag_test(); @@ -141,7 +140,7 @@ task direct_test(); logic [4:0] flag_opcodes_2[3] = {ALU_SLL, ALU_SRL, ALU_SRA}; logic [4:0] flag_opcodes_3[2] = {ALU_ADD, ALU_SUB}; logic [4:0] flag_opcodes_4[5] = {ALU_XOR, ALU_OR, ALU_AND, ALU_EQ, ALU_NE}; - + repeat(100) begin std::randomize(operand_a_i, operand_b_i) with {operand_a_i==operand_b_i;}; @@ -191,14 +190,14 @@ task direct_test(); repeat(100) begin - std::randomize(operand_a_i) with {operand_a_i > {31{1'b1}};}; + std::randomize(operand_a_i) with {operand_a_i > {31{1'b1}};}; std::randomize(operand_b_i) with {operand_b_i > {31{1'b1}};}; foreach(flag_opcodes_3[i]) begin operator_i = flag_opcodes_3[i]; @(posedge clk); end end - + repeat(100) begin operand_a_i = $urandom(); @@ -256,7 +255,7 @@ parameter HASH_LEN = 1000; parameter START_CODING = 10366; parameter START_MUX = START_CODING+100; -module alu_riscv_ref ( +module alu_ref ( input logic [ALUOP_W-1:0] alu_op_i, input logic [OP_W-1:0] a_i, input logic [OP_W-1:0] b_i, diff --git a/Labs/03. Register file and memory/README.md b/Labs/03. Register file and memory/README.md index ae8f96f2..7b6c2f8a 100644 --- a/Labs/03. Register file and memory/README.md +++ b/Labs/03. Register file and memory/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 3 "Регистровый файл и память инструкций" +# Лабораторная работа №3 "Регистровый файл и память инструкций" Процессор — это программно-управляемое устройство, выполняющее обработку информации и управление этим процессом. Очевидно, программа, которая управляет процессором, должна где-то храниться. Данные, с которыми процессор работает, тоже должны быть в доступном месте. Нужна память! @@ -6,46 +6,45 @@ Описать на языке SystemVerilog элементы памяти для будущего процессора: -- память инструкций (Instruction Memory); -- регистровый файл (Register File). +- память инструкций; +- регистровый файл. ## Материалы для подготовки к лабораторной работе -Для успешного выполнения лабораторной работы, вам необходимо освоить: +В дополнение к [материалам](../../Basic%20Verilog%20structures/), изученным в ходе предыдущих работ, вам рекомендуется ознакомиться с: -- приведенные способы описания [мультиплексоров](../../Basic%20Verilog%20structures/Multiplexors.md) -- способы описания [регистров](../../Basic%20Verilog%20structures/Registers.md) +- способами описания [регистров](../../Basic%20Verilog%20structures/Registers.md) на языке SystemVerilog. ## Ход работы 1. Изучить способы организации памяти (раздел [#теория про память](#теория-про-память)). 2. Изучить конструкции SystemVerilog для реализации запоминающих элементов (раздел [#инструменты](#инструменты-для-реализации-памяти)). -3. В проекте с прошлой лабораторной реализовать модули: Instruction Memory и Register File ([#задание](#задание-по-реализации-памяти)). -4. Проверить с помощью тестового окружения корректность их работы. +3. Реализовать модули памяти инструкции и регистрового файла. +4. Проверить с помощью верификационного окружения корректность их работы. 5. Проверить работу регистрового файла в ПЛИС. ## Теория про память Память — это устройство для упорядоченного хранения и выдачи информации. Различные запоминающие устройства отличаются способом и организацией хранения данных. Базовыми характеристиками памяти являются: -- V — объем (количество бит данных, которые единовременно может хранить память); +- V — объём (количество бит данных, которые единовременно может хранить память); - a — разрядность адреса (ширина шины адреса, определяет адресное пространство — количество адресов отдельных ячеек памяти); - d — разрядность хранимых данных (разрядность ячейки памяти, как правило совпадает с разрядностью входных/выходных данных). В общем случае `V = 2^a * d`. -Для объема памяти в 1 KiB ([кибибайт](https://ru.wikipedia.org/wiki/%D0%9A%D0%B8%D0%B1%D0%B8%D0%B1%D0%B0%D0%B9%D1%82), 1024 байта или 8192 бита) разрядность адреса может быть, например, 10 бит (что покрывает 2^10 = 1024 адреса), тогда разрядность хранимых данных должна быть 8 бит. 1024 * 8 = 8192, то есть 1 кибибайт. Если разрядность адреса, например, 8 бит (что покрывает 2^8 = 256 адресов), то разрядность данных `d = V / 2^a` это 8192 / 256 = 32 бита. +Для объема памяти в 1 KiB ([кибибайт](https://ru.wikipedia.org/wiki/%D0%9A%D0%B8%D0%B1%D0%B8%D0%B1%D0%B0%D0%B9%D1%82), 1024 байта или 8192 бита) разрядность адреса может быть, например, 10 бит (что покрывает 2^10 = 1024 адреса), тогда разрядность хранимых данных должна быть 8 бит. 1024 * 8 = 8192, то есть 1 кибибайт. Если разрядность адреса составляет 8 бит (что покрывает 2^8 = 256 адресов), то разрядность данных `d = V / 2^a` это 8192 / 256 = 32 бита. -Однако, может быть такое, что не все ячейки памяти реально реализованы на кристалле микросхемы, то есть некоторые адреса существуют, но по ним не имеет смысла обращаться, а объем памяти, соответственно, не равен `V ≠ 2^a * d` — он меньше. Подобные случаи будут рассмотрены отдельно. +Однако, может быть такое, что не все ячейки памяти реализованы на кристалле микросхемы, то есть некоторые адреса существуют, но по ним не имеет смысла обращаться, а объем памяти, соответственно, не равен `V ≠ 2^a * d` — он меньше. -Память можно разделить на категории: ПЗУ (постоянное запоминающее устройство) и ОЗУ (оперативное запоминающее устройство). Из ПЗУ можно только считывать информацию, которая попадает в ПЗУ до начала использования памяти и не может изменяться в процессе работы. Из ОЗУ можно считывать и записывать информацию. В самом простом случае ПЗУ имеет один вход адреса `addr` и один выход считываемых данных `read_data`. На вход `addr` подается адрес требуемой ячейки памяти, на выходе `read_data` появляются данные, которые хранятся по этому адресу. +Память можно разделить на категории: ПЗУ (постоянное запоминающее устройство) и ОЗУ (оперативное запоминающее устройство). Из ПЗУ можно только считывать информацию, которая попадает в него до начала использования памяти и не может изменяться в процессе работы. Из ОЗУ можно считывать и записывать информацию. В самом простом случае ПЗУ имеет один вход адреса `addr` и один выход считываемых данных `read_data`. На вход `addr` подается адрес требуемой ячейки памяти, на выходе `read_data` появляются данные, которые хранятся по этому адресу. Для ОЗУ требуется больше сигналов. Кроме входного `addr` и выходного `read_data` добавляются: входные данные для записи `write_data`, сигнал синхронизации `clk`, который определяет момент записи данных и сигнал разрешения на запись `write_enable`, который контролирует нужно ли записывать данные или только считывать. Для того, чтобы записать информацию в такую память необходимо: - выставить адрес `addr` в который планируется запись данных, - выставить сами данные для записи на вход `write_data`, - установить сигнал `write_enable` в состояние разрешения записи (как правило это 1) и -- дождаться нужного фронта `clk` — в этот момент данные будут записаны по указанному адресу. При этом, на выходе `read_data` будут старые данные, хранящиеся по адресу `addr`. На одном такте происходит одновременное считывание информации и запись новой. +- дождаться нужного (положительного, либо отрицательного) фронта `clk` — в этот момент данные будут записаны по указанному адресу. Так же возможна реализация, в которой вход `write_data` и выход `read_data` объединены в единый вход/выход `data`. В этом случае операции чтения и записи разделены во времени и используют для этого один единый порт ввода-вывода (`inout`, двунаправленный порт) `data`. @@ -53,9 +52,9 @@ _Рисунок 1. Примеры блоков ПЗУ и ОЗУ._ -Кроме того, различают память с **синхронным** и **асинхронным** чтением. В первом случае, перед выходным сигналом шины данных ставится дополнительный регистр, в который по тактовому синхроимпульсу записываются запрашиваемые данные. Такой способ может очень сильно сократить **критический путь** цифровой схемы, но требует дополнительный такт на доступ в память. В свою очередь, асинхронное чтение позволяет получить данные, не дожидаясь очередного синхроимпульса, но такой способ увеличивает критический путь. +Кроме того, различают память с **синхронным** и **асинхронным** чтением. В первом случае, перед выходным сигналом шины данных ставится дополнительный регистр, в который по тактовому синхроимпульсу записываются запрашиваемые данные. Такой способ может значительно сократить **критический путь** цифровой схемы, но требует дополнительный такт на доступ в память. В свою очередь, асинхронное чтение позволяет получить данные, не дожидаясь очередного синхроимпульса, но такой способ увеличивает критический путь. -Еще одной характеристикой памяти является количество доступных портов. Количество портов определяет к скольким ячейкам памяти можно обратиться одновременно. Проще говоря, сколько входов адреса существует. Все примеры памяти рассмотренные выше являются **однопортовыми**, то есть у них один порт. Например, если у памяти 2 входа адреса `addr1` и `addr2` — это **двухпортовая память**. При этом не важно, можно ли по этим адресам только читать/писать или выполнять обе операции. +Еще одной характеристикой памяти является количество доступных портов чтения или записи (не путайте с портами модуля, которые являются любыми его входными/выходными сигналами). Количество портов определяет к скольким ячейкам памяти можно обратиться одновременно. Проще говоря, сколько входов адреса существует. Все примеры памяти рассмотренные выше являются **однопортовыми**, то есть у них один порт. Например, если у памяти 2 входа адреса `addr1` и `addr2` — это **двухпортовая память**. При этом не важно, можно ли по этим адресам только читать/писать или выполнять обе операции. Регистровый файл, который будет реализован в рамках данной работы, является **трехпортовым**, и имеет 2 порта на чтение и 1 порт на запись. @@ -65,7 +64,7 @@ _Рисунок 1. Примеры блоков ПЗУ и ОЗУ._ _Рисунок 2. Структурная схема логического блока в ПЛИС[[1]](https://en.wikipedia.org/wiki/Field-programmable_gate_array)._ -В логическом блоке есть **таблицы подстановки** (Look Up Table, LUT), которые представляют собой не что иное как память, которая переконфигурируется под нужды хранения, а не реализацию логики. Таким образом, трехвходовой LUT может выступать в роли 8-битной памяти. +В логическом блоке есть **таблицы подстановки** (Look Up Table, LUT), которые представляют собой не что иное как память, которая конфигурируется под нужды хранения, а не реализацию логики. Таким образом, трехвходовой LUT может выступать в роли 8-битной памяти. Однако LUT будет сложно приспособить под многопортовую память: посмотрим на схему еще раз: три входа LUT формируют адрес одной из восьми ячеек. Это означает, что среди этих восьми ячеек нельзя обратиться к двум из них одновременно. @@ -75,9 +74,9 @@ _Рисунок 2. Структурная схема логического бл Минусом является ограниченность в реализации многопортовой памяти. -Сравним блочную память с распределенной/регистровой: поскольку большой объем памяти съест много логических блоков при реализации распределенной/регистровой памяти, такую память лучше делать в виде блочной. +Сравним блочную память с распределенной/регистровой: поскольку большой объем памяти "съест" много логических блоков при реализации распределенной/регистровой памяти, такую память лучше делать в виде блочной. -В то же время, к плюсам распределенной/регистровой памяти можно отнести возможность синтезировать память с асинхронным портом на чтение, чем мы и воспользуемся при реализации однотактного процессора (если бы порт чтения памяти был синхронным, нам потребовалось ждать один такт, чтобы получить инструкцию из памяти инструкций или данные из регистрового файла, что затруднило бы реализацию однотактного процессора, где каждая инструкция должна выполняться ровно за один такт). +В то же время, к плюсам распределенной/регистровой памяти относится возможность синтезировать память с асинхронным портом на чтение, чем мы и воспользуемся при реализации однотактного процессора (если бы порт чтения памяти был синхронным, нам потребовалось ждать один такт, чтобы получить инструкцию из памяти инструкций или данные из регистрового файла, что затруднило бы реализацию однотактного процессора, где каждая инструкция должна выполняться ровно за один такт). Обычно синтезатор сам понимает, какой вид памяти подходит под описанную схему на языке SystemVerilog. @@ -89,13 +88,13 @@ _Рисунок 2. Структурная схема логического бл Память на языке SystemVerilog объявляется [подобно регистрам](../../Basic%20Verilog%20structures/Registers.md), используя ключевое слово `logic`. Но, кроме разрядности (разрядности ячеек памяти, в данном случае) после имени регистра (памяти, в данном случае) указывается количество создаваемых ячеек либо в виде натурального числа, либо в виде диапазона адресов этих ячеек.: -```SystemVerilog +```Verilog logic [19:0] memory1 [16]; // memory1 и memory2 являются полностью logic [19:0] memory2 [0:15]; // идентичными памятями. logic [19:0] memory3 [15:0]; // memory3 будет такой же памятью, что и // предыдущие, но на временной диаграмме - // Vivado при ее отображении сперва будут + // Vivado при её отображении сперва будут // идти ячейки, начинающиеся со старших // адресов (что в рамках данного курса // лабораторных работ будет скорее минусом). @@ -113,13 +112,13 @@ logic [19:0] memory3 [1:16]; // А вот memory3 хоть и совпадае _Листинг 1. Пример создания массива ячеек._ -В приведенном листинге `logic [19:0] memory1 [16];` создается память с шестнадцатью (от 0-го до 15-го адреса) 20-битными ячейками памяти. В таком случае говорят, что ширина памяти 20 бит, а глубина 16. Для адресации такой памяти потребуется адрес с разрядностью ceil(log2(16)) = 4 бита (`ceil` — операция округления вверх). Это однопортовая память. +В приведенном листинге `logic [19:0] memory1 [16];` создается память с шестнадцатью (от 0-го до 15-го адреса) 20-битными ячейками памяти. В таком случае говорят, что ширина памяти 20 бит, а глубина 16. Для адресации такой памяти потребуется адрес с разрядностью ceil(log2(16)) = 4 бита (`ceil` — операция округления вверх). Для обращения к конкретной ячейке памяти используются квадратные скобки с указанием нужного адреса `memory[addr]`. Грубо говоря, то, что указывается в квадратных скобках будет подключено ко входу адреса памяти `memory`. -Чтение из памяти может быть сделано двумя способами: синхронно и асинхронно. +Как уже говорилось, чтение из памяти может быть сделано двумя способами: синхронно и асинхронно. -Синхронное чтение подразумевает ожидание следующего тактового синхроимпульса для выдачи данных после получения адреса. Иными словами, данные будут установлены на выходе не в тот же такт, когда был выставлен адрес на вход памяти данных, а на следующий. Не смотря на то, что в таком случае на каждой операции чтения "теряется" один такт, память с синхронным чтением имеет значительно меньший критический путь, чем положительно сказывается на временных характеристиках итоговой схемы. +Синхронное чтение подразумевает ожидание следующего тактового синхроимпульса для выдачи данных после получения адреса. Иными словами, данные будут установлены на выходе не в тот же такт, когда был выставлен адрес на вход памяти данных, а на следующий. Несмотря на то, что в таком случае на каждой операции чтения "теряется" один такт, память с синхронным чтением имеет значительно меньший критический путь, чем положительно сказывается на временных характеристиках итоговой схемы. Память с асинхронным чтением выдает данные в том же такте, что и получает адрес (т.е. ведет себя как комбинационная схема). Несмотря на то, что такой подход кажется быстрее, память с асинхронным чтением обладает длинным критическим путем, причем чем большего объема будет память, тем длиннее будет критический путь. @@ -127,14 +126,14 @@ _Листинг 1. Пример создания массива ячеек._ Так как запись в память является синхронным событием, то описывается она в конструкции `always_ff`. При этом, как и при описании регистра, можно реализовать управляющий сигнал разрешения на запись через блок вида `if(write_enable)`. -```SystemVerilog +```Verilog module mem16_20 ( // создать блок с именем mem16_20 - input logic clk, // вход синхронизации - input logic [3:0] addr, // адресный вход - input logic [19:0] write_data, // вход данных для записи - input logic write_enable, // сигнал разрешения на запись - output logic [19:0] async_read_data// асинхронный выход считанных данных - output logic [19:0] sync_read_data // синхронный выход считанных данных + input logic clk, // вход синхронизации + input logic [3:0] addr, // адресный вход + input logic [19:0] write_data, // вход данных для записи + input logic write_enable, // сигнал разрешения на запись + output logic [19:0] async_read_data,// асинхронный выход считанных данных + output logic [19:0] sync_read_data // синхронный выход считанных данных ); logic [19:0] memory [0:15]; // создать память с 16-ю @@ -162,7 +161,7 @@ endmodule _Листинг 2. Пример описания портов памяти._ -В случае реализации ПЗУ нет необходимости в описании входов для записи, поэтому описание памяти занимает всего пару строк. Чтобы проинициализировать такую память (то есть поместить в нее начальные значения, которые можно было бы из нее читать), требуемое содержимое нужно добавить к прошивке, вместе с которой данные попадут в ПЛИС. Для этого в проект добавляется текстовый файл формата `.mem` с содержимым памяти. Для того, чтобы отметить данный файл в качестве инициализирующего память, можно использовать системную функцию `$readmemh`. +В случае реализации ПЗУ нет необходимости в описании входов для записи, поэтому описание памяти занимает всего пару строк. Чтобы проинициализировать такую память (то есть поместить в неё начальные значения, которые можно было бы считать), требуемое содержимое нужно добавить к прошивке, вместе с которой данные попадут в ПЛИС. Для этого в проект добавляется текстовый файл формата `.mem` с содержимым памяти. Для того, чтобы отметить данный файл в качестве инициализирующего память, можно использовать системную функцию `$readmemh`. У данной функции есть два обязательных аргумента: @@ -171,20 +170,24 @@ _Листинг 2. Пример описания портов памяти._ и два опциональных: -- стартовый адрес, начиная с которого память будет проинициализирована данным файлом (по-умолчанию равен нулю) +- стартовый адрес, начиная с которого память будет проинициализирована данным файлом (по умолчанию равен нулю) - конечный адрес, на котором инициализация закончится (даже если в файле были ещё какие-то данные). Пример полного вызова выглядит так: -`$readmemh("",,,);` +```Verilog +$readmemh("",,,); +``` Однако на деле обычно используются только обязательные аргументы: -`$readmemh("",);` +```Verilog +$readmemh("",); +``` Пример описанной выше памяти: -```SystemVerilog +```Verilog module rom16_8 ( input logic [3:0] addr1, // первый 4-битный адресный вход input logic [3:0] addr2, // второй 4-битный адресный вход @@ -207,7 +210,7 @@ endmodule _Листинг 3. Пример использования инициализирующей функции $readmemh._ -Содержимое файла `rom_data.mem`, к примеру может быть таким (каждая строка соответствует значению отдельной ячейки памяти, начиная со стартового адреса): +Содержимое файла `rom_data.mem`, к примеру, может быть таким (каждая строка соответствует значению отдельной ячейки памяти, начиная со стартового адреса): ```hex FA @@ -233,16 +236,16 @@ _Листинг 3. Пример использования инициализи - 32-битный вход адреса - 32-битный выход данных (асинхронное чтение) -```SystemVerilog -mоdulе instr_mеm( - inрut logic [31:0] addr_i, - оutрut logic [31:0] rеаd_dаtа_o +```Verilog +module instr_mem( + input logic [31:0] read_addr_i, + output logic [31:0] read_data_o ); ``` -Не смотря на разрядность адреса, на практике, внутри данного модуля вы должны будете реализовать память с 1024-мя 32-битными ячейками (в ПЛИС попросту не хватит ресурсов на реализации памяти с 232 ячеек). Таким образом, реально будет использоваться только 10 бит адреса. +Несмотря на разрядность адреса, на практике, внутри данного модуля вы должны будете реализовать память с 512-ю 32-битными ячейками (в ПЛИС попросту не хватит ресурсов на реализации памяти с 232 ячеек). Таким образом, реально будет использоваться только 9 бит адреса. -При этом по спецификации процессор RISC-V использует память с побайтовой адресацией. Байтовая адресация означает, что процессор способен обращаться к отдельным байтам в памяти (за каждым байтом памяти закреплен свой индивидуальный адрес). +При этом по спецификации процессор RISC-V использует память с побайтовой адресацией [[2](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf), стр. 15]. Байтовая адресация означает, что процессор способен обращаться к отдельным байтам в памяти (за каждым байтом памяти закреплен свой индивидуальный адрес). Однако, если у памяти будут 32-битные ячейки, доступ к конкретному байту будет осложнен, ведь каждая ячейка — это 4 байта. Как получить данные третьего байта памяти? Если обратиться к третьей ячейке в массиве — придут данные 12-15-ых байт (поскольку каждая ячейка содержит по 4 байта). Чтобы получить данные третьего байта, необходимо **разделить значение пришедшего адреса на 4** (отбросив остаток от деления). `3 / 4 = 0` — и действительно, если обратиться к нулевой ячейке памяти — будут получены данные 3-го, 2-го, 1-го и 0-го байт. То, что помимо значения третьего байта есть еще данные других байт нас в данный момент не интересует, важна только сама возможность указать адрес конкретного байта. @@ -250,18 +253,18 @@ mоdulе instr_mеm( _Рисунок 3. Связь адреса байта и индекса слова в массиве ячеек памяти._ -Деление на 2n можно осуществить, отбросив `n` младших бит числа. Учитывая то, что для адресации 1024 ячеек памяти мы будем использовать 10 бит адреса, память инструкций должна выдавать на выход данные, расположенные по адресу `addr_i[11:2]`. +Деление на 2n можно осуществить, отбросив `n` младших бит числа. Учитывая то, что для адресации 512 ячеек памяти мы будем использовать 9 бит адреса, память инструкций должна выдавать на выход данные, расположенные по адресу `addr_i[10:2]`. -Не смотря на заданный размер памяти инструкций в 1024 32-битных ячейки, на практике удобно параметризовать это значение, чтобы в ситуациях, когда требуется меньше или больше памяти можно было получить обновленное значение, не переписывая код во множестве мест. Подобное новшество вы сможете оценить на практике, получив возможность существенно сокращать время синтеза процессора, уменьшая размер памяти до необходимого минимума путем изменения значения одного лишь параметра. +Несмотря на зафиксированный заданием размер памяти инструкций в 512 32-битных ячейки, на практике удобно параметризовать это значение, чтобы в ситуациях, когда требуется меньше или больше памяти можно было получить обновленный модуль, не переписывая код во множестве мест. Подобное новшество вы сможете оценить на практике, получив возможность существенно сокращать время синтеза процессора, уменьшая размер памяти до необходимого минимума путем изменения значения одного лишь параметра. -Для этого можно например создать параметр: `INSTR_MEM_SIZE_BYTES`, показывающий размер памяти инструкций в байтах. Однако, поскольку у данной памяти 32-битные ячейки, нам было бы удобно иметь и параметр `INSTR_MEM_SIZE_WORDS`, который говорит сколько в памяти 32-битных ячеек. +Для этого можно, например, создать параметр: `INSTR_MEM_SIZE_BYTES`, показывающий размер памяти инструкций в байтах. Однако, поскольку у данной памяти 32-битные ячейки, нам было бы удобно иметь и параметр `INSTR_MEM_SIZE_WORDS`, который говорит сколько в памяти 32-битных ячеек. При этом `INSTR_MEM_SIZE_WORDS = INSTR_MEM_SIZE_BYTES / 4` (т.е. в 32-битном слове 4 байта). -В случае подобной параметризации, необходимо иметь возможность подстраивать количество используемых бит адреса. Для 1024 ячеек памяти мы использовали 10 бит адреса, для 512 ячеек нам потребуется уже 9 бит. Нетрудно заметить, что нам нужно такое число бит данных, возведя в степень которого `2`, мы получим размер нашей памяти (либо число, превышающее этот размер в случае, если размер памяти не является степенью двойки). Иными словами, нам нужен логарифм по основанию 2 от размера памяти, с округлением до целого вверх. И неудивительно, что в SystemVerilog есть специальная конструкция, которая позволяет считать подобные числа. Эта конструкция называется `$clog2` (`с` означает "ceil" — операцию округления вверх). +В случае подобной параметризации, необходимо иметь возможность подстраивать количество используемых бит адреса. Для 512 ячеек памяти мы использовали 9 бит адреса, для 1024 ячеек нам потребуется уже 10 бит. Нетрудно заметить, что нам нужно такое число бит данных, возведя в степень которого `2`, мы получим размер нашей памяти (либо число, превышающее этот размер в случае, если размер памяти не является степенью двойки). Иными словами, нам нужен логарифм по основанию 2 от размера памяти, с округлением до целого вверх. И неудивительно, что в SystemVerilog есть специальная конструкция, которая позволяет считать подобные числа. Эта конструкция называется `$clog2` (`с` означает "ceil" — операцию округления вверх). Поскольку реализация памяти состоит буквально из нескольких строчек, но при этом использование параметров может вызвать некоторые затруднения, код памяти инструкций предоставляется в готовом виде: -```SystemVerilog +```Verilog module instr_mem import memory_pkg::INSTR_MEM_SIZE_BYTES; import memory_pkg::INSTR_MEM_SIZE_WORDS; @@ -278,10 +281,10 @@ import memory_pkg::INSTR_MEM_SIZE_WORDS; $readmemh("program.mem", ROM); // поместить в память ROM содержимое end // файла program.mem - // Реализация асинхронного порта на чтение, где на выход идет ячейка памяти + // Реализация асинхронного порта на чтение, где на выход идёт ячейка памяти // инструкций, расположенная по адресу read_addr_i, в котором обнулены два // младших бита, а также биты, двоичный вес которых превышает размер памяти - // данных в байтах. + // данных в байтах. // Два младших бита обнулены, чтобы обеспечить выровненный доступ к памяти, // в то время как старшие биты обнулены, чтобы не дать обращаться в память // по адресам несуществующих ячеек (вместо этого будут выданы данные ячеек, @@ -295,7 +298,7 @@ _Листинг 4. SystemVerilog-описание памяти инструкц ### 3. Регистровый файл -На языке SystemVerilog необходимо реализовать модуль регистрового файла (`rf_r𝚒sсv`) для процессора с архитектурой RISC-V, представляющего собой трехпортовое ОЗУ с двумя портами на чтение и одним портом на запись и состоящей из 32-х 32-битных регистров, объединенных в массив с именем `rf_mem`. +На языке SystemVerilog необходимо реализовать модуль регистрового файла для процессора с архитектурой RISC-V, представляющего собой трехпортовое ОЗУ с двумя портами на чтение и одним портом на запись и состоящей из 32-х 32-битных регистров, объединенных в массив с именем `rf_mem`. У данного модуля будет восемь входных/выходных сигналов: @@ -308,18 +311,18 @@ _Листинг 4. SystemVerilog-описание памяти инструкц - 32-битный выход данных асинхронного чтения по первому адресу - 32-битный выход данных асинхронного чтения по второму адресу -```SystemVerilog -mоdulе rf_r𝚒sсv( - inрut logic сlk_i, - inрut logic write_enable_i, +```Verilog +module register_file( + input logic clk_i, + input logic write_enable_i, - inрut logic [ 4:0] write_addr_i, - inрut logic [ 4:0] read_addr1_i, - inрut logic [ 4:0] read_addr2_i, + input logic [ 4:0] write_addr_i, + input logic [ 4:0] read_addr1_i, + input logic [ 4:0] read_addr2_i, - inрut logic [31:0] write_data_i, - оutрut logic [31:0] read_data1_o, - оutрut logic [31:0] read_data2_o + input logic [31:0] write_data_i, + output logic [31:0] read_data1_o, + output logic [31:0] read_data2_o ); ``` @@ -333,34 +336,21 @@ mоdulе rf_r𝚒sсv( ## Порядок выполнения работы -1. Внимательно ознакомьтесь с заданием. В случае возникновения вопросов, проконсультируйтесь с преподавателем. -2. Добавьте в проект файл [`memory_pkg.sv`](memory_pkg.sv). Этот файл содержит объявление пакета `memory_pkg`, в котором прописаны размеры памяти инструкций и памяти данных (реализуется позднее). -3. Реализуйте память инструкций. Для этого: - 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `instr_mem.sv`. - 2. Опишите в нем модуль памяти инструкций по предоставленному в _листинге 4_ коду. -4. Реализуйте регистровый файл. Для этого: - 1. В `Design Sources` проекта создайте `SystemVerilog`-файл `rf_riscv.sv`. - 2. Опишите в нем модуль регистрового файла с таким же именем и портами, как указано в задании. - 1. Обратите внимание, что имя памяти (не название модуля, а имя массива регистров внутри модуля) должно быть `rf_mem`. Такое имя необходимо для корректной работы верификационного окружения. - 2. Как и у памяти инструкций, порты чтения регистрового файла должны быть **асинхронными**. - 3. Не забывайте, что у вас 2 порта на чтение и 1 порт на запись, при этом каждый порт не зависит от остальных (в модуле 3 независимых входа адреса). - 4. Чтение из нулевого регистра (чтение по адресу 0) всегда должно возвращать нулевое значение. Этого можно добиться двумя путями: - 1. Путем добавления мультиплексора перед выходным сигналом чтения (мультиплексор будет определять, пойдут ли на выход данные из ячейки регистрового файла, либо, в случае если адрес равен нулю, на выход пойдет константа ноль). - 2. Путем инициализации нулевого регистра нулевым значением и запретом записи в этот регистр (при записи и проверки write_enable добавить дополнительную проверку на адрес). - 3. Каким образом будет реализована эта особенность регистрового файла не важно, выберите сами. - 3. После описания регистрового файла его необходимо проверить с помощью [`тестового окружения`](../../Basic%20Verilog%20structures/Testbench.md). - 1. Тестовое окружение находится [`здесь`](tb_rf_riscv.sv). - 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_rf_riscv`). - 4. **Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!** -5. Проверьте работоспособность вашей цифровой схемы в ПЛИС. Для этого: - 1. Добавьте файлы из папки [`board files`](https://github.com/MPSU/APS/tree/master/Labs/03.%20Register%20file%20and%20memory/board%20files) в проект. - 1. Файл [nexys_rf_riscv.sv](https://github.com/MPSU/APS/tree/master/Labs/03.%20Register%20file%20and%20memory/board%20files/nexys_rf_riscv.sv) необходимо добавить в `Design Sources` проекта. - 2. Файл [nexys_a7_100t.xdc](https://github.com/MPSU/APS/tree/master/Labs/03.%20Register%20file%20and%20memory/board%20files/nexys_a7_100t.xdc) необходимо добавить в `Constraints` проекта. В случае, если вы уже добавляли одноименный файл в рамках предыдущих лабораторных работ, его содержимое необходимо заменить содержимым нового файла. - 2. Выберите `nexys_rf_riscv` в качестве модуля верхнего уровня (`top-level`). - 3. Выполните генерацию битстрима и сконфигурируйте ПЛИС. Для этого воспользуйтесь [следующей инструкцией](../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). - 4. Описание логики работы модуля верхнего уровня и связи периферии ПЛИС с реализованным модулем находится в папке [`board files`](https://github.com/MPSU/APS/tree/master/Labs/03.%20Register%20file%20and%20memory/board%20files). +1. Добавьте в проект файл [`memory_pkg.sv`](memory_pkg.sv). Этот файл содержит объявление пакета `memory_pkg`, в котором прописаны размеры памяти инструкций и памяти данных (реализуется позднее). +2. Реализуйте память инструкций посредством описания, представленного в _листинге 4_. +3. Опишите регистровый файл с таким же именем и портами, как указано в задании. + 1. Обратите внимание, что имя памяти (не название модуля, а имя массива регистров внутри модуля) должно быть `rf_mem`. Такое имя необходимо для корректной работы верификационного окружения. + 2. Как и у памяти инструкций, порты чтения регистрового файла должны быть **асинхронными**. + 3. Не забывайте, что у вас 2 порта на чтение и 1 порт на запись, при этом каждый порт не зависит от остальных (в модуле 3 независимых входа адреса). + 4. Чтение из нулевого регистра (чтение по адресу 0) всегда должно возвращать нулевое значение. Этого можно добиться двумя путями: + 1. Путем добавления мультиплексора перед выходным сигналом чтения (мультиплексор будет определять, пойдут ли на выход данные из ячейки регистрового файла, либо, в случае если адрес равен нулю, на выход пойдет константа ноль). + 2. Путем инициализации нулевого регистра нулевым значением и запретом записи в этот регистр (при записи и проверки write_enable добавить дополнительную проверку на адрес). + 3. Каким образом будет реализована эта особенность регистрового файла не важно, выберите сами. +4. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_03.tb_register_file.sv`](lab_03.tb_register_file.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +5. Проверьте работоспособность вашей цифровой схемы в ПЛИС. ## Источники 1. [Field-programmable gate array](https://en.wikipedia.org/wiki/Field-programmable_gate_array) +2. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA, Document Version 20240411, Editors Andrew Waterman and Krste Asanović, RISC-V Foundation, April 2024](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf) diff --git a/Labs/03. Register file and memory/board files/README.md b/Labs/03. Register file and memory/board files/README.md index ce890478..d203405a 100644 --- a/Labs/03. Register file and memory/board files/README.md +++ b/Labs/03. Register file and memory/board files/README.md @@ -2,7 +2,7 @@ После того, как вы проверили на моделировании регистровый файл, вам необходимо проверить его работу на прототипе в ПЛИС. -Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). +Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/07.%20Program%20and%20debug.md). На _рис. 1_ представлена схема прототипа в ПЛИС. diff --git a/Labs/03. Register file and memory/tb_rf_riscv.sv b/Labs/03. Register file and memory/lab_03.tb_register_file.sv similarity index 97% rename from Labs/03. Register file and memory/tb_rf_riscv.sv rename to Labs/03. Register file and memory/lab_03.tb_register_file.sv index b5454933..6ed9aa27 100644 --- a/Labs/03. Register file and memory/tb_rf_riscv.sv +++ b/Labs/03. Register file and memory/lab_03.tb_register_file.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_rf_riscv(); +module lab_03_tb_register_file(); logic CLK; logic [ 4:0] RA1; @@ -22,7 +22,7 @@ module tb_rf_riscv(); logic [31:0] RD1ref; logic [31:0] RD2ref; - rf_riscv DUT( + register_file DUT( .clk_i (CLK), .read_addr1_i (RA1), .read_addr2_i (RA2), @@ -33,7 +33,7 @@ module tb_rf_riscv(); .read_data2_o (RD2) ); - rf_riscv_ref DUTref( + register_file_ref DUTref( .clk_i (CLK ), .read_addr1_i (RA1 ), .read_addr2_i (RA2 ), @@ -61,7 +61,7 @@ module tb_rf_riscv(); initial begin $timeformat (-9, 2, "ns"); - $display( "\nStart test: \n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop(); + $display("Test has been started"); RA1 = 'b1; @(posedge CLK); if (32'hx !== RD1) begin @@ -153,7 +153,7 @@ module tb_rf_riscv(); end endmodule -module rf_riscv_ref( +module register_file_ref( input logic clk_i, input logic write_enable_i, diff --git a/Labs/03. Register file and memory/program.mem b/Labs/03. Register file and memory/program.mem deleted file mode 100644 index b45a373e..00000000 --- a/Labs/03. Register file and memory/program.mem +++ /dev/null @@ -1,1024 +0,0 @@ -524c0895 -609b1f05 -b0d46df9 -46589375 -30106d7c -1761e8dc -7ed462df -9f9e3372 -4c5d513d -7e5bbd27 -61247ecd -9f2e7769 -ae8e2ca4 -95cde8e2 -82db2a72 -26305738 -28010642 -5aaceccc -e968983b -80da9a7d -d6beaa62 -a02d7563 -91de7c57 -92305096 -aca9e314 -df2452e6 -341ec4b3 -378c48a1 -deb5b026 -9c657151 -2bc150fd -a0bb897b -18527f25 -03b1d063 -27e0aaa4 -bf16c9c4 -7622635f -59f47b9a -9f8dbcd6 -69fae7d9 -05b44de3 -249e8233 -0d7a8c7f -f96061d7 -ec26457e -a771ef2e -b12bf050 -16d1cd9e -f1fe9ebf -f85bc148 -95b248b4 -e3f150ca -358c33f3 -c8ece2ff -9fa937db -773d18bb -82fd9d29 -65f22290 -df7e59b3 -ae62d28d -129f682e -cdab29fb -2b5efbe9 -9792231f -cd015090 -dab076fc -ddccd5eb -2fde1f10 -d562779e -66785313 -9b69c0e8 -779dc2bc -f9449c65 -c04acb7c -9dba6fcd -7d9b6a42 -276653b4 -db64a937 -a4602749 -1f734980 -ab444018 -92896900 -0c7975c9 -4080e414 -6fd149e0 -01c43356 -8da9eb7c -766b855c -4735d719 -35e7d477 -5d58d24f -0b91b876 -79660392 -f26db461 -37d6e5f0 -7863c03f -b7eed8d8 -6cf3ced2 -4fab0bce -917d0f57 -186a8639 -9f59ab48 -a2960b17 -4c5cc01b -b4bb98c4 -6ecc5501 -14ed44b8 -da9d0ca8 -90ef3346 -09f152fb -d2ac6b5f -f9e9c6de -8796464e -5ca09ff4 -96be3b7a -db65d059 -bc45c829 -392da269 -b7fc3339 -5faf9279 -132dece5 -d84f249a -fca54a87 -8a14765a -afb85e51 -0ef64c83 -536bab14 -68f35cdb -788d73fb -19b493e4 -f289684e -f4be169b -61e06b30 -dec0c039 -bd1c3761 -4419dbf6 -9d829efe -256adac2 -ae28273e -373ec50b -d12dc034 -239f287b -ca115890 -d81641b8 -52750d5f -e1f82223 -358cb5c8 -3148919b -096d8ace -555f6c38 -b2b7ab11 -3ad93c12 -f84d3a8e -6e7dcef9 -6496de5b -fc92883b -ea11546d -5faa2e62 -583be754 -472ff4f3 -268b7dfa -d8678232 -73820769 -6287b0da -ac560283 -f741cde7 -ab01e1c8 -32a3124d -515f0b14 -d17a18be -3c9c3360 -b2560f69 -28dad67e -707165b7 -d0cb8ade -c3b5b60e -2f7cf14c -85c2ab8f -a20d00b1 -972da3d8 -5dd86dcf -94bbccc4 -a9efef06 -27b47e27 -a0371c12 -31d58f93 -f44ca9cb -4e0f6a17 -5527c1e5 -78da95fc -784236af -28c48487 -117352d6 -784d55bb -f7cb0520 -dbac5a16 -ea692831 -332d1050 -c498a64b -03d25f20 -05c23907 -7869caf7 -ab46851e -02c467c4 -707b5224 -911746af -e4b39e48 -bec11fe0 -fa464e16 -439ef837 -4d433836 -19d58312 -98224d2b -ad91000b -c1c49b16 -1e3954b8 -8c8aed72 -d3a4226a -82b1c8d7 -c12a97f0 -959e82b9 -a6e4bf52 -7db53527 -3bb80797 -c0fb4e8d -20c2ca81 -9c567d73 -590a8e4d -4693b83c -54aec375 -d9be20e9 -1b8dbe6f -e86984d5 -3775ad6c -5d403878 -77674a1a -b8b00f25 -bdbc0764 -fc2e2ecd -a30d7b2e -a6657fbb -ee97c41a -c06f7857 -7e1cab47 -eee304e4 -ee59420e -d6414b43 -ee1bbbc5 -56e40aaf -9d53437d -1f0d5780 -30ff8dc4 -c7d472e9 -ce21e664 -6a977ebb -05bd7a23 -524c0895 -609b1f05 -b0d46df9 -46589375 -30106d7c -1761e8dc -7ed462df -9f9e3372 -4c5d513d -7e5bbd27 -61247ecd -9f2e7769 -ae8e2ca4 -95cde8e2 -82db2a72 -26305738 -28010642 -5aaceccc -e968983b -80da9a7d -d6beaa62 -a02d7563 -91de7c57 -92305096 -aca9e314 -df2452e6 -341ec4b3 -378c48a1 -deb5b026 -9c657151 -2bc150fd -a0bb897b -18527f25 -03b1d063 -27e0aaa4 -bf16c9c4 -7622635f -59f47b9a -9f8dbcd6 -69fae7d9 -05b44de3 -249e8233 -0d7a8c7f -f96061d7 -ec26457e -a771ef2e -b12bf050 -16d1cd9e -f1fe9ebf -f85bc148 -95b248b4 -e3f150ca -358c33f3 -c8ece2ff -9fa937db -773d18bb -82fd9d29 -65f22290 -df7e59b3 -ae62d28d -129f682e -cdab29fb -2b5efbe9 -9792231f -cd015090 -dab076fc -ddccd5eb -2fde1f10 -d562779e -66785313 -9b69c0e8 -779dc2bc -f9449c65 -c04acb7c -9dba6fcd -7d9b6a42 -276653b4 -db64a937 -a4602749 -1f734980 -ab444018 -92896900 -0c7975c9 -4080e414 -6fd149e0 -01c43356 -8da9eb7c -766b855c -4735d719 -35e7d477 -5d58d24f -0b91b876 -79660392 -f26db461 -37d6e5f0 -7863c03f -b7eed8d8 -6cf3ced2 -4fab0bce -917d0f57 -186a8639 -9f59ab48 -a2960b17 -4c5cc01b -b4bb98c4 -6ecc5501 -14ed44b8 -da9d0ca8 -90ef3346 -09f152fb -d2ac6b5f -f9e9c6de -8796464e -5ca09ff4 -96be3b7a -db65d059 -bc45c829 -392da269 -b7fc3339 -5faf9279 -132dece5 -d84f249a -fca54a87 -8a14765a -afb85e51 -0ef64c83 -536bab14 -68f35cdb -788d73fb -19b493e4 -f289684e -f4be169b -61e06b30 -dec0c039 -bd1c3761 -4419dbf6 -9d829efe -256adac2 -ae28273e -373ec50b -d12dc034 -239f287b -ca115890 -d81641b8 -52750d5f -e1f82223 -358cb5c8 -3148919b -096d8ace -555f6c38 -b2b7ab11 -3ad93c12 -f84d3a8e -6e7dcef9 -6496de5b -fc92883b -ea11546d -5faa2e62 -583be754 -472ff4f3 -268b7dfa -d8678232 -73820769 -6287b0da -ac560283 -f741cde7 -ab01e1c8 -32a3124d -515f0b14 -d17a18be -3c9c3360 -b2560f69 -28dad67e -707165b7 -d0cb8ade -c3b5b60e -2f7cf14c -85c2ab8f -a20d00b1 -972da3d8 -5dd86dcf -94bbccc4 -a9efef06 -27b47e27 -a0371c12 -31d58f93 -f44ca9cb -4e0f6a17 -5527c1e5 -78da95fc -784236af -28c48487 -117352d6 -784d55bb -f7cb0520 -dbac5a16 -ea692831 -332d1050 -c498a64b -03d25f20 -05c23907 -7869caf7 -ab46851e -02c467c4 -707b5224 -911746af -e4b39e48 -bec11fe0 -fa464e16 -439ef837 -4d433836 -19d58312 -98224d2b -ad91000b -c1c49b16 -1e3954b8 -8c8aed72 -d3a4226a -82b1c8d7 -c12a97f0 -959e82b9 -a6e4bf52 -7db53527 -3bb80797 -c0fb4e8d -20c2ca81 -9c567d73 -590a8e4d -4693b83c -54aec375 -d9be20e9 -1b8dbe6f -e86984d5 -3775ad6c -5d403878 -77674a1a -b8b00f25 -bdbc0764 -fc2e2ecd -a30d7b2e -a6657fbb -ee97c41a -c06f7857 -7e1cab47 -eee304e4 -ee59420e -d6414b43 -ee1bbbc5 -56e40aaf -9d53437d -1f0d5780 -30ff8dc4 -c7d472e9 -ce21e664 -6a977ebb -05bd7a23 -524c0895 -609b1f05 -b0d46df9 -46589375 -30106d7c -1761e8dc -7ed462df -9f9e3372 -4c5d513d -7e5bbd27 -61247ecd -9f2e7769 -ae8e2ca4 -95cde8e2 -82db2a72 -26305738 -28010642 -5aaceccc -e968983b -80da9a7d -d6beaa62 -a02d7563 -91de7c57 -92305096 -aca9e314 -df2452e6 -341ec4b3 -378c48a1 -deb5b026 -9c657151 -2bc150fd -a0bb897b -18527f25 -03b1d063 -27e0aaa4 -bf16c9c4 -7622635f -59f47b9a -9f8dbcd6 -69fae7d9 -05b44de3 -249e8233 -0d7a8c7f -f96061d7 -ec26457e -a771ef2e -b12bf050 -16d1cd9e -f1fe9ebf -f85bc148 -95b248b4 -e3f150ca -358c33f3 -c8ece2ff -9fa937db -773d18bb -82fd9d29 -65f22290 -df7e59b3 -ae62d28d -129f682e -cdab29fb -2b5efbe9 -9792231f -cd015090 -dab076fc -ddccd5eb -2fde1f10 -d562779e -66785313 -9b69c0e8 -779dc2bc -f9449c65 -c04acb7c -9dba6fcd -7d9b6a42 -276653b4 -db64a937 -a4602749 -1f734980 -ab444018 -92896900 -0c7975c9 -4080e414 -6fd149e0 -01c43356 -8da9eb7c -766b855c -4735d719 -35e7d477 -5d58d24f -0b91b876 -79660392 -f26db461 -37d6e5f0 -7863c03f -b7eed8d8 -6cf3ced2 -4fab0bce -917d0f57 -186a8639 -9f59ab48 -a2960b17 -4c5cc01b -b4bb98c4 -6ecc5501 -14ed44b8 -da9d0ca8 -90ef3346 -09f152fb -d2ac6b5f -f9e9c6de -8796464e -5ca09ff4 -96be3b7a -db65d059 -bc45c829 -392da269 -b7fc3339 -5faf9279 -132dece5 -d84f249a -fca54a87 -8a14765a -afb85e51 -0ef64c83 -536bab14 -68f35cdb -788d73fb -19b493e4 -f289684e -f4be169b -61e06b30 -dec0c039 -bd1c3761 -4419dbf6 -9d829efe -256adac2 -ae28273e -373ec50b -d12dc034 -239f287b -ca115890 -d81641b8 -52750d5f -e1f82223 -358cb5c8 -3148919b -096d8ace -555f6c38 -b2b7ab11 -3ad93c12 -f84d3a8e -6e7dcef9 -6496de5b -fc92883b -ea11546d -5faa2e62 -583be754 -472ff4f3 -268b7dfa -d8678232 -73820769 -6287b0da -ac560283 -f741cde7 -ab01e1c8 -32a3124d -515f0b14 -d17a18be -3c9c3360 -b2560f69 -28dad67e -707165b7 -d0cb8ade -c3b5b60e -2f7cf14c -85c2ab8f -a20d00b1 -972da3d8 -5dd86dcf -94bbccc4 -a9efef06 -27b47e27 -a0371c12 -31d58f93 -f44ca9cb -4e0f6a17 -5527c1e5 -78da95fc -784236af -28c48487 -117352d6 -784d55bb -f7cb0520 -dbac5a16 -ea692831 -332d1050 -c498a64b -03d25f20 -05c23907 -7869caf7 -ab46851e -02c467c4 -707b5224 -911746af -e4b39e48 -bec11fe0 -fa464e16 -439ef837 -4d433836 -19d58312 -98224d2b -ad91000b -c1c49b16 -1e3954b8 -8c8aed72 -d3a4226a -82b1c8d7 -c12a97f0 -959e82b9 -a6e4bf52 -7db53527 -3bb80797 -c0fb4e8d -20c2ca81 -9c567d73 -590a8e4d -4693b83c -54aec375 -d9be20e9 -1b8dbe6f -e86984d5 -3775ad6c -5d403878 -77674a1a -b8b00f25 -bdbc0764 -fc2e2ecd -a30d7b2e -a6657fbb -ee97c41a -c06f7857 -7e1cab47 -eee304e4 -ee59420e -d6414b43 -ee1bbbc5 -56e40aaf -9d53437d -1f0d5780 -30ff8dc4 -c7d472e9 -ce21e664 -6a977ebb -05bd7a23 -524c0895 -609b1f05 -b0d46df9 -46589375 -30106d7c -1761e8dc -7ed462df -9f9e3372 -4c5d513d -7e5bbd27 -61247ecd -9f2e7769 -ae8e2ca4 -95cde8e2 -82db2a72 -26305738 -28010642 -5aaceccc -e968983b -80da9a7d -d6beaa62 -a02d7563 -91de7c57 -92305096 -aca9e314 -df2452e6 -341ec4b3 -378c48a1 -deb5b026 -9c657151 -2bc150fd -a0bb897b -18527f25 -03b1d063 -27e0aaa4 -bf16c9c4 -7622635f -59f47b9a -9f8dbcd6 -69fae7d9 -05b44de3 -249e8233 -0d7a8c7f -f96061d7 -ec26457e -a771ef2e -b12bf050 -16d1cd9e -f1fe9ebf -f85bc148 -95b248b4 -e3f150ca -358c33f3 -c8ece2ff -9fa937db -773d18bb -82fd9d29 -65f22290 -df7e59b3 -ae62d28d -129f682e -cdab29fb -2b5efbe9 -9792231f -cd015090 -dab076fc -ddccd5eb -2fde1f10 -d562779e -66785313 -9b69c0e8 -779dc2bc -f9449c65 -c04acb7c -9dba6fcd -7d9b6a42 -276653b4 -db64a937 -a4602749 -1f734980 -ab444018 -92896900 -0c7975c9 -4080e414 -6fd149e0 -01c43356 -8da9eb7c -766b855c -4735d719 -35e7d477 -5d58d24f -0b91b876 -79660392 -f26db461 -37d6e5f0 -7863c03f -b7eed8d8 -6cf3ced2 -4fab0bce -917d0f57 -186a8639 -9f59ab48 -a2960b17 -4c5cc01b -b4bb98c4 -6ecc5501 -14ed44b8 -da9d0ca8 -90ef3346 -09f152fb -d2ac6b5f -f9e9c6de -8796464e -5ca09ff4 -96be3b7a -db65d059 -bc45c829 -392da269 -b7fc3339 -5faf9279 -132dece5 -d84f249a -fca54a87 -8a14765a -afb85e51 -0ef64c83 -536bab14 -68f35cdb -788d73fb -19b493e4 -f289684e -f4be169b -61e06b30 -dec0c039 -bd1c3761 -4419dbf6 -9d829efe -256adac2 -ae28273e -373ec50b -d12dc034 -239f287b -ca115890 -d81641b8 -52750d5f -e1f82223 -358cb5c8 -3148919b -096d8ace -555f6c38 -b2b7ab11 -3ad93c12 -f84d3a8e -6e7dcef9 -6496de5b -fc92883b -ea11546d -5faa2e62 -583be754 -472ff4f3 -268b7dfa -d8678232 -73820769 -6287b0da -ac560283 -f741cde7 -ab01e1c8 -32a3124d -515f0b14 -d17a18be -3c9c3360 -b2560f69 -28dad67e -707165b7 -d0cb8ade -c3b5b60e -2f7cf14c -85c2ab8f -a20d00b1 -972da3d8 -5dd86dcf -94bbccc4 -a9efef06 -27b47e27 -a0371c12 -31d58f93 -f44ca9cb -4e0f6a17 -5527c1e5 -78da95fc -784236af -28c48487 -117352d6 -784d55bb -f7cb0520 -dbac5a16 -ea692831 -332d1050 -c498a64b -03d25f20 -05c23907 -7869caf7 -ab46851e -02c467c4 -707b5224 -911746af -e4b39e48 -bec11fe0 -fa464e16 -439ef837 -4d433836 -19d58312 -98224d2b -ad91000b -c1c49b16 -1e3954b8 -8c8aed72 -d3a4226a -82b1c8d7 -c12a97f0 -959e82b9 -a6e4bf52 -7db53527 -3bb80797 -c0fb4e8d -20c2ca81 -9c567d73 -590a8e4d -4693b83c -54aec375 -d9be20e9 -1b8dbe6f -e86984d5 -3775ad6c -5d403878 -77674a1a -b8b00f25 -bdbc0764 -fc2e2ecd -a30d7b2e -a6657fbb -ee97c41a -c06f7857 -7e1cab47 -eee304e4 -ee59420e -d6414b43 -ee1bbbc5 -56e40aaf -9d53437d -1f0d5780 -30ff8dc4 -c7d472e9 -ce21e664 -6a977ebb -05bd7a23 \ No newline at end of file diff --git a/Labs/04. Primitive programmable device/README.md b/Labs/04. Primitive programmable device/README.md index b8357a6e..ac7414a1 100644 --- a/Labs/04. Primitive programmable device/README.md +++ b/Labs/04. Primitive programmable device/README.md @@ -1,16 +1,12 @@ -# Лабораторная работа 4 "Простейшее программируемое устройство" +# Лабораторная работа №4 "Простейшее программируемое устройство" -В этой лабораторной работе на основе ранее разработанных блоков памяти и АЛУ вы соберете простой учебный процессор с архитектурой `CYBERcobra 3000 Pro 2.1`. Это нужно для более глубокого понимания принципов работы программируемых устройств, чтобы проще было понять архитектуру RISC-V в будущем. +В этой лабораторной работе, на основе ранее разработанных блоков памяти и АЛУ, вы соберете простой учебный процессор с архитектурой `CYBERcobra 3000 Pro 2.1`. Это нужно для более глубокого понимания принципов работы программно-управляемых устройств, чтобы проще было понять архитектуру RISC-V в будущем. -## Материал для подготовки к лабораторной работе +## Материалы для подготовки к лабораторной работе -Для выполнения этой лабораторной работы, необходимо в полной мере освоить следующие элементы синтаксиса языка SystemVerilog: +В дополнение к [материалам](../../Basic%20Verilog%20structures/), изученным в ходе предыдущих работ, вам рекомендуется ознакомиться с: -1. Описание модулей, их создание внутри других модулей и оператор непрерывного присваивания `assign` ([Modules.md](../../Basic%20Verilog%20structures/Modules.md)). -2. Описание мультиплексоров: с помощью `тернарного оператора`, блоков `case` и `if/else`. Знать особенности использования этих блоков и особенности синтеза комбинационной логики внутри блока `always` ([Multiplexors.md](../../Basic%20Verilog%20structures/Multiplexors.md)). -3. Описание регистров ([Registers.md](../../Basic%20Verilog%20structures/Registers.md)). -4. Оператор конкатенации ([Concatenation.md](../../Basic%20Verilog%20structures/Concatenation.md)). -5. Отладку проекта по временной диаграмме ([Debug manual.md](../../Vivado%20Basics/Debug%20manual.md)). +- Оператором конкатенации ([Concatenation.md](../../Basic%20Verilog%20structures/Concatenation.md)). ## Цель @@ -19,18 +15,18 @@ ## Ход работы 1. Изучить принцип работы процессоров (соответствующий раздел [#теории](#теория-про-программируемое-устройство)) -2. Познакомиться с архитектурой и микроархитектурой `CYBERcobra 3000 Pro 2.1` (раздел про эту [#архитектуру](#архитектура-cybercobra-3000-pro-21-и-ее-микроархитектура)) +2. Познакомиться с архитектурой и микроархитектурой `CYBERcobra 3000 Pro 2.1` (раздел про эту [#архитектуру](#архитектура-cybercobra-3000-pro-21-и-её-микроархитектура)) 3. Изучить необходимые для описания процессора конструкции SystemVerilog (раздел [#инструменты](#инструменты-для-реализации-процессора)) 4. Реализовать процессор с архитектурой `CYBERcobra 3000 Pro 2.1` ([#задание по разработке аппаратуры](#задание-по-реализации-процессора)) 5. Проверить работу процессора в ПЛИС. Доп. задание, выполняемое дома: -6. Написать программу для процессора и на модели убедиться в корректности ее выполнения ([Индивидуальное задание](Индивидуальное%20задание)). +6. Написать программу для процессора и на модели убедиться в корректности её выполнения ([Индивидуальное задание](Индивидуальное%20задание)). ## Теория про программируемое устройство -В обобщенном виде, процессор включает в себя память, АЛУ, устройство управления и интерфейсную логику для организации ввода/вывода. Также, в процессоре есть специальный регистр `PC` (**Program Counter** – счетчик команд), который хранит в себе число – адрес ячейки памяти, где хранится инструкция, которую нужно выполнить. Инструкция тоже представляет собой число, в котором закодировано `что нужно сделать` и `с чем это нужно сделать`. +В обобщенном виде, процессор включает в себя память, АЛУ, устройство управления и интерфейсную логику для организации ввода/вывода. Также, в процессоре есть специальный регистр `PC` (**Program Counter** – счётчик команд), который хранит в себе число – адрес ячейки памяти, где хранится инструкция, которую нужно выполнить. Инструкция тоже представляет собой число, в котором закодировано `что нужно сделать` и `с чем это нужно сделать`. Алгоритм работы процессора следующий: @@ -42,19 +38,19 @@ Любая инструкция приводит к изменению состояния памяти. В случае процессора с архитектурой `CYBERcobra 3000 Pro 2.1` есть два класса инструкций: одни изменяют содержимое регистрового файла — это инструкции записи. Другие изменяют значение `PC` — это инструкции перехода. В первом случае используются вычислительные инструкции и инструкции загрузки данных из других источников. Во-втором случае используются инструкции перехода. -Если процессор обрабатывает вычислительную инструкцию, то `PC` перейдет к следующей по порядку инструкции. На лабораторной работе, посвященной памяти, мы сделали память инструкций с [побайтовой адресацией](../03.%20Register%20file%20and%20memory/README.md#1-память-инструкций). Это означает, что каждый байт памяти имеет свой собственный адрес. Поскольку длина инструкции составляет `4 байта`, для перехода к следующей инструкции `PC` должен быть увеличен на `4` (`PC = PC + 4`). При этом, регистровый файл сохранит результат некоторой операции на АЛУ или данные со входного порта. +Если процессор обрабатывает вычислительную инструкцию, то `PC` перейдет к следующей по порядку инструкции. В ЛР№3 мы реализовали память инструкций с [побайтовой адресацией](../03.%20Register%20file%20and%20memory/README.md#1-память-инструкций). Это означает, что каждый байт памяти имеет свой собственный адрес. Поскольку длина инструкции составляет `4 байта`, для перехода к следующей инструкции `PC` должен быть увеличен на `4` (`PC = PC + 4`). При этом, регистровый файл сохранит результат некоторой операции на АЛУ или данные с порта входных данных. Если же обрабатывается инструкция перехода, то возможно два варианта. В случае безусловного или успешного условного перехода, значение `PC` увеличится на значение константы, закодированной внутри инструкции `PC = PC + const*4` (иными словами, `const` говорит о том, через сколько инструкций перепрыгнет `PC`, `const` может быть и отрицательной). В случае же неуспешного условного перехода `PC`, как и после вычислительных команд, просто перейдет к следующей инструкции, то есть `PC = PC + 4`. > Строго говоря `PC` меняется при выполнении любой инструкции (кроме случая `const = 0`, то есть перехода инструкции на саму себя `PC = PC + 0*4`). Разница в том, на какое значение `PC` изменится. В вычислительных инструкциях это всегда адрес следующей инструкции, программа не управляет `PC`, он "сам знает", что ему делать. В инструкциях перехода программа и контекст определяют, что произойдет с `PC`. -## Архитектура CYBERcobra 3000 Pro 2.1 и ее микроархитектура - -В качестве первого разрабатываемого программируемого устройства предлагается использовать архитектуру специального назначения `CYBERcobra 3000 Pro 2.1`, которая была разработана в **МИЭТ**. Главным достоинством данной архитектуры является простота ее понимания и реализации. Главным ее минусом является неоптимальность ввиду неэффективной реализации кодирования инструкций, что приводит к наличию неиспользуемых битов в программах. Но это неважно, так как основная цель разработки процессора с архитектурой `CYBERcobra 3000 Pro 2.1` — это более глубокое понимание принципов работы программируемых устройств, которое поможет при разработке более сложного процессора с архитектурой **RISC-V**. +## Архитектура CYBERcobra 3000 Pro 2.1 и её микроархитектура ![../../.pic/Labs/lab_04_cybercobra/logoCC3000.svg](../../.pic/Labs/lab_04_cybercobra/logoCC3000.svg) -Простота архитектуры `CYBERcobra 3000 Pro 2.1` проявляется, в том числе, за счет отсутствия памяти данных. Это значит, что данные c которыми работает программа могут храниться только в регистровом файле. Также в таком процессоре почти полностью отсутствует устройство управления (формально оно существует, но состоит только из проводов и пары логических вентилей). +В качестве первого разрабатываемого программируемого устройства предлагается использовать архитектуру специального назначения `CYBERcobra 3000 Pro 2.1`, которая была разработана в **МИЭТ**. Главным достоинством данной архитектуры является простота её понимания и реализации. Главным её минусом является неоптимальность ввиду неэффективной реализации кодирования инструкций, что приводит к наличию неиспользуемых битов в программах. Но это неважно, так как основная цель разработки процессора с архитектурой `CYBERcobra 3000 Pro 2.1` — это более глубокое понимание принципов работы программируемых устройств, которое поможет при разработке более сложного процессора с архитектурой **RISC-V**. + +Простота архитектуры `CYBERcobra 3000 Pro 2.1` проявляется, в том числе, за счёт отсутствия памяти данных. Это значит, что данные c которыми работает программа могут храниться только в регистровом файле. Также в таком процессоре почти полностью отсутствует устройство управления (формально оно существует, но состоит только из проводов и пары логических вентилей). Архитектурой предусмотрена поддержка 19 инструкций (5 типов команд): @@ -73,9 +69,9 @@ ### Последовательное считывание инструкций -Будем рассматривать архитектуру (функции процессора) и микроархитектуру (реализация процессора) одновременно, прослеживая рассуждения их разработчика. +Будем рассматривать архитектуру (функциональные возможности процессора) и микроархитектуру (реализацию процессора) одновременно, прослеживая рассуждения их разработчика. -Для начала реализуем базовый функционал, подключив счетчик команд `PC` к памяти инструкций `instr_mem` и сумматору, прибавляющему 4 к `PC`. Выход сумматора подключим ко входу `PC`. +Для начала реализуем базовый функционал, подключив счётчик команд `PC` к памяти инструкций `instr_mem` и сумматору, прибавляющему 4 к `PC`. Выход сумматора подключим ко входу `PC`. Каждый раз, когда будет происходить тактовый импульс (переключение `clk_i` из 0 в 1), значение `PC` будет увеличиваться на `4`, тем самым указывая на следующую инструкцию. Последовательное считывание программы из памяти готово. @@ -111,14 +107,16 @@ _Таблица 1. Кодирование вычислительных инст ### Реализация вычислительных инструкций -Чтобы процессор правильно реагировал на эти инструкции, требуется подключить ко входам адреса регистрового файла и управляющему входу АЛУ соответствующие биты выхода `read_data` памяти инструкции (**Instruction Memory**). В таком случае, когда `PC` будет указывать на ячейку памяти, в которой лежит, например, следующая 32-битная инструкция: +Чтобы процессор правильно реагировал на эти инструкции, требуется подключить ко входам адреса регистрового файла и управляющему входу АЛУ соответствующие биты выхода `read_data_o` памяти инструкции (**Instruction Memory**). В таком случае, когда `PC` будет указывать на ячейку памяти, в которой лежит, например, следующая 32-битная инструкция: ```text 0000 00111 00100 01000 00000000 11100 |alu_op| RA1 | RA2 | | WA ``` -будет выполнена операция `reg_file[28] = reg_file[4] | reg_file[8]`, потому что `alu_op = 00111`, что соответствует операции **логического ИЛИ**, `WA = 11100`, то есть 28-ой регистр, `RA1 = 00100` (4-ый регистр) и `RA2 = 01000` (8-ой регистр). _Рис. 1_ иллюстрирует фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ. Так как пока что другие инструкции не поддерживаются, то вход `WE` регистрового файла всегда равен `1` (это временно). +будет выполнена операция `reg_file[28] = reg_file[4] | reg_file[8]`, потому что `alu_op = 00111`, что соответствует операции **логического ИЛИ** (см ЛР№2), `WA = 11100`, то есть запись произойдёт в 28-ой регистр, `RA1 = 00100` и `RA2 = 01000` — это значит что данные для АЛУ будут браться из 4-го и 8-го регистров соответственно. + +_Рис. 1_ иллюстрирует фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ. Поскольку другие инструкции пока что не поддерживаются, то вход `WE` регистрового файла просто равен `1` (это временно). ![../../.pic/Labs/lab_04_cybercobra/ppd_1.drawio.svg](../../.pic/Labs/lab_04_cybercobra/ppd_1.drawio.svg) @@ -183,7 +181,7 @@ _Таблица 3. Кодирование в инструкции большег На _рис. 3_ приводится фрагмент микроархитектуры, поддерживающий вычислительные операции на АЛУ, загрузку констант из инструкции в регистровый файл и загрузку данных с внешних устройств. -По аналогии с загрузкой констант увеличиваем входной мультиплексор до 4 входов и подключаем к нему управляющие сигналы – `[29:28]` биты инструкции. Последний вход используется, чтобы разрешить неопределенность на выходе при `WS == 3`(`default`-вход, см. [мультиплексор](../../Basic%20Verilog%20structures/Multiplexors.md)). +По аналогии с загрузкой констант увеличиваем входной мультиплексор до 4 входов и подключаем к нему управляющие сигналы – `[29:28]` биты инструкции. Последний вход используется, чтобы разрешить неопределённость на выходе при `WS == 3`(`default`-вход, см. [мультиплексор](../../Basic%20Verilog%20structures/Multiplexors.md)). Выход OUT подключается к первому порту на чтение регистрового файла. Значение на выходе OUT будет определяться содержимым ячейки памяти по адресу `RA1`. @@ -201,9 +199,9 @@ _Таблица 4.Кодирование условного перехода._ Для вычисления результата условного перехода, нам необходимо выполнить операцию на АЛУ и посмотреть на сигнал `flag`. Если он равен 1, переход выполняется, в противном случае — не выполняется. А значит, нам нужны операнды `A`, `B`, и `alu_op`. Кроме того, нам необходимо указать насколько мы сместимся относительно текущего значения `PC` (константу смещения, `offset`). Для передачи этой константы лучше всего подойдут незадействованные биты инструкции `[12:5]`. -Обратим внимание на то, что `PC` 32-битный и должен быть всегда кратен четырем (`PC` не может указывать кроме как в начало инструкции, а каждая инструкция длиной в 32 бита). Кратные четырем двоичные числа всегда будут иметь в конце два нуля (так же, как и кратные ста десятичные числа). Поэтому для более эффективного использования бит константы смещения, эти два нуля будут неявно подразумеваться при её описании. При этом, перед увеличением программного счетчика на значение константы смещения, эти два нуля нужно будет к ней приклеить справа. Кроме того, чтобы разрядность константы совпадала с разрядностью `PC`, необходимо знакорасширить её до 32 бит. +Обратим внимание на то, что `PC` 32-битный и должен быть всегда кратен четырем (`PC` не может указывать кроме как в начало инструкции, а каждая инструкция длиной в 32 бита). Кратные четырем двоичные числа всегда будут иметь в конце два нуля (так же, как и кратные ста десятичные числа). Поэтому для более эффективного использования бит константы смещения, эти два нуля будут неявно подразумеваться при её описании. При этом, перед увеличением программного счётчика на значение константы смещения, эти два нуля нужно будет к ней приклеить справа. Кроме того, чтобы разрядность константы совпадала с разрядностью `PC`, необходимо знакорасширить её до 32 бит. -Предположим, мы хотим переместиться на две инструкции вперед. Это означает, что программный счетчик должен будет увеличиться на 8 ([2 инструкции] * [4 байта — размер одной инструкции в памяти]). Умножение на 4 константы смещения произойдет путем добавления к ней двух нулей справа, поэтому в поле `offset` мы просто записываем число инструкций, на которые мы переместим программный счетчик (на две): `0b00000010`. +Предположим, мы хотим переместиться на две инструкции вперед. Это означает, что программный счётчик должен будет увеличиться на 8 ([2 инструкции] * [4 байта — размер одной инструкции в памяти]). Умножение на 4 константы смещения произойдет путем добавления к ней двух нулей справа, поэтому в поле `offset` мы просто записываем число инструкций, на которые мы переместим программный счётчик (на две): `0b00000010`. Данный Си-подобный псевдокод (далее мы назовем его псевдоассемблером) демонстрирует кодирование инструкций с новым полем `B`: @@ -214,9 +212,9 @@ _Таблица 4.Кодирование условного перехода._ PC ← PC + 4 ``` -Так как второй вход сумматора счетчика команд занят числом 4, то для реализации условного перехода этот вход надо мультиплексировать с константой. Мультиплексор при этом управляется 30-ым битом `B`, который и определяет, что будет прибавляться к `PC`. +Так как второй вход сумматора счётчика команд занят числом 4, то для реализации условного перехода этот вход надо мультиплексировать с константой. Мультиплексор при этом управляется 30-ым битом `B`, который и определяет, что будет прибавляться к `PC`. -Сигнальные линии, которые управляют АЛУ и подают на его входы операнды уже существуют. Поэтому на схему необходимо добавить только логику управления мультиплексором на входе сумматора счетчика команд так. Эта логика работает следующим образом: +Сигнальные линии, которые управляют АЛУ и подают на его входы операнды уже существуют. Поэтому на схему необходимо добавить только логику управления мультиплексором на входе сумматора счётчика команд так. Эта логика работает следующим образом: 1. если сейчас инструкция условного перехода 2. и если условие перехода выполнилось, @@ -326,14 +324,14 @@ _Рисунок 5. Реализация безусловного переход - Арифметико-логическое устройство - 32-битный сумматор -Кроме того, необходимо описать регистр счетчика команд и логику его работы, в соответствии с ранее представленной микроархитектурой. +Кроме того, необходимо описать регистр счётчика команд и логику его работы, в соответствии с ранее представленной микроархитектурой. -```SystemVerilog -module CYВЕRcоbrа ( - inрut logic clk_i, - inрut logic rst_i, - inрut logic [15:0] sw_i, - оutрut logic [31:0] out_o +```Verilog +module CYBERcobra ( + input logic clk_i, + input logic rst_i, + input logic [15:0] sw_i, + output logic [31:0] out_o ); endmodule @@ -341,32 +339,25 @@ endmodule ## Порядок выполнения задания -1. Внимательно ознакомьтесь с [заданием](#задание-по-реализации-процессора). В случае возникновения вопросов, проконсультируйтесь с преподавателем. -2. Реализуйте модуль `CYBERcobra`. Для этого: - 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `cybercobra.sv`. - 2. Опишите в нем модуль процессора с таким же именем и портами, как указано в [задании](#задание-по-реализации-процессора) (обратите внимание на регистр имени модуля). - 1. В первую очередь, необходимо создать счетчик команд и все вспомогательные провода. При создании, **следите за разрядностью**. - 2. Затем, необходимо создать экземпляры модулей: памяти инструкции, АЛУ, регистрового файла и сумматора. При подключении сигналов сумматора, надо **обязательно** надо подать нулевое значение на входной бит переноса. Выходной бит переноса подключать не обязательно. Объекту памяти инструкций нужно дать имя `imem`. - 3. После этого, необходимо описать оставшуюся логику: - 1. Программного счетчика - 2. Сигнала управления мультиплексором, выбирающим слагаемое для программного счетчика - 3. Сигнала разрешения записи в регистровый файл - 4. Мультиплексор, выбирающий слагаемое для программного счетчика - 5. Мультиплексор, выбирающий источник записи в регистровый файл. -3. После описания модуля его необходимо проверить с помощью [`тестового окружения`](../../Basic%20Verilog%20structures/Testbench.md). - 1. Тестовое окружение находится [`здесь`](tb_cybercobra.sv). - 2. Программа, которой необходимо проинициализировать память инструкций находится в файле [program.mem](program.mem). Алгоритм работы программы приведен в разделе [`Финальный обзор`](#финальный-обзор). - 3. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 4. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня. - 5. **Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!** - 6. В этот раз, в конце не будет сообщения о том, работает ли ваше устройство или в нем есть ошибки. Вы должны самостоятельно проверить работу модуля, перенеся его внутренние сигналы на временную диаграмму, и [проверив](../../Vivado%20Basics/Debug%20manual.md) логику их работы. -4. Проверьте работоспособность вашей цифровой схемы в ПЛИС. Для этого: - 1. Добавьте файлы из папки [`board files`](https://github.com/MPSU/APS/tree/master/Labs/04.%20Primitive%20programmable%20device/board%20files) в проект. - 1. Файл [nexys_cybercobra.sv](https://github.com/MPSU/APS/tree/master/Labs/04.%20Primitive%20programmable%20device/board%20files/nexys_cybercobra.sv) необходимо добавить в `Design Sources` проекта. - 2. Файл [nexys_a7_100t.xdc](https://github.com/MPSU/APS/tree/master/Labs/04.%20Primitive%20programmable%20device/board%20files/nexys_a7_100t.xdc) необходимо добавить в `Constraints` проекта. В случае, если вы уже добавляли одноименный файл в рамках предыдущих лабораторных работ, его содержимое необходимо заменить содержимым нового файла. - 2. Выберите `nexys_cybercobra` в качестве модуля верхнего уровня (`top-level`). - 3. Выполните генерацию битстрима и сконфигурируйте ПЛИС. Для этого воспользуйтесь [следующей инструкцией](../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). - 4. Описание логики работы модуля верхнего уровня и связи периферии ПЛИС с реализованным модулем находится в папке [`board files`](https://github.com/MPSU/APS/tree/master/Labs/04.%20Primitive%20programmable%20device/board%20files). +1. Добавьте в `Design Sources` проекта файл [program.mem](program.mem). +2. Опишите модуль `CYBERcobra` с таким же именем и портами, как указано в задании (обратите внимание на регистр имени модуля). + 1. В первую очередь, необходимо создать счётчик команд и все вспомогательные провода. При создании, **следите за разрядностью**. + 2. Затем, необходимо создать экземпляры модулей: памяти инструкции, АЛУ, регистрового файла и сумматора. При подключении сигналов сумматора, надо **обязательно** надо подать нулевое значение на входной бит переноса. Выходной бит переноса подключать не обязательно. Объекту памяти инструкций нужно дать имя `imem`. + 3. После этого, необходимо описать оставшуюся логику: + 1. Программного счётчика + 2. Сигнала управления мультиплексором, выбирающим слагаемое для программного счётчика + 3. Сигнала разрешения записи в регистровый файл + 4. Мультиплексор, выбирающий слагаемое для программного счётчика + 5. Мультиплексор, выбирающий источник записи в регистровый файл. +3. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_04.tb_cybercobra.sv`](lab_04.tb_cybercobra.sv). + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. + 2. В этот раз, в конце не будет сообщения о том, работает ли ваше устройство или в нем есть ошибки. Вам необходимо самостоятельно проверить работу модуля, перенеся его внутренние сигналы на временную диаграмму, и [изучив](../../Vivado%20Basics/05.%20Bug%20hunting.md) их поведение. + 3. По сути, проверка сводится к потактовому изучению временной диаграммы, во время которого вам нужно циклично ответить на следующие вопросы (после чего необходимо сравнить предсказанный ответ со значением сигналов на временной диаграмме): + 1. Какое сейчас значение программного счётчика? + 2. Какое должно быть значение у ячейки памяти инструкций с адресом, соответствующим значению программного счётчика. Какой инструкции соответствует значение этой ячейки памяти? + 3. Как должно обновиться содержимое регистрового файла в результате выполнения этой инструкции: должно ли записаться какое-либо значение? Если да, то какое и по какому адресу? + 4. Как должен измениться программный счётчик после выполнения этой инструкции? +4. Проверьте работоспособность вашей цифровой схемы в ПЛИС. --- diff --git a/Labs/04. Primitive programmable device/board files/README.md b/Labs/04. Primitive programmable device/board files/README.md index 6ba4ab8a..19120e03 100644 --- a/Labs/04. Primitive programmable device/board files/README.md +++ b/Labs/04. Primitive programmable device/board files/README.md @@ -2,7 +2,7 @@ После того, как вы проверили на моделировании дизайн, вам необходимо проверить его работу на прототипе в ПЛИС. -Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). +Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/07.%20Program%20and%20debug.md). На _рис. 1_ представлена схема прототипа в ПЛИС. diff --git a/Labs/04. Primitive programmable device/tb_cybercobra.sv b/Labs/04. Primitive programmable device/lab_04.tb_cybercobra.sv similarity index 81% rename from Labs/04. Primitive programmable device/tb_cybercobra.sv rename to Labs/04. Primitive programmable device/lab_04.tb_cybercobra.sv index 87c7226a..198dcb53 100644 --- a/Labs/04. Primitive programmable device/tb_cybercobra.sv +++ b/Labs/04. Primitive programmable device/lab_04.tb_cybercobra.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_CYBERcobra(); +module lab_04_tb_CYBERcobra(); CYBERcobra dut( .clk_i(clk), @@ -26,7 +26,7 @@ module tb_CYBERcobra(); always #5 clk = ~clk; initial begin - $display( "\nStart test: \n\n===============================================\nAdd CYBERcobra signals to the waveform and then\nCLICK THE BUTTON 'Run All'\n===============================================\n"); $stop(); + $display("Test has been started"); rstn = 1'b1; #10; rstn = 1'b0; diff --git a/Labs/05. Main decoder/README.md b/Labs/05. Main decoder/README.md index 6e31c934..1a807d9d 100644 --- a/Labs/05. Main decoder/README.md +++ b/Labs/05. Main decoder/README.md @@ -1,16 +1,16 @@ -# Лабораторная работа 5 "Декодер инструкций" +# Лабораторная работа №5 "Декодер инструкций" Устройство управления (УУ) – один из базовых блоков процессора, функцией которого является декодирование инструкций и выдача управляющих сигналов для всех блоков процессора. Роль УУ в данном курсе (с некоторыми оговорками) будет играть декодер инструкций. ## Цель -Описать на языке **SystemVerilog** блок декодера инструкций (модуль **decoder_riscv**) для однотактного процессора с архитектурой **RISC-V**. +Описать на языке **SystemVerilog** блок декодера инструкций для однотактного процессора с архитектурой **RISC-V**. -## Материал для подготовки к лабораторной работе +## Материалы для подготовки к лабораторной работе -- Изучить форматы кодирования инструкций базового набора команд [`RV32I`](../../Other/rv32i.md). -- Изучить [теорию по регистрам контроля и статуса](../../Other/CSR.md). -- Изучить [различия между блокирующими и неблокирующими присваиваниями](../../Basic%20Verilog%20structures/Assignments.md). +- [Форматы кодирования инструкций базового набора команд `RV32I`](../../Other/rv32i.md). +- [Теорию по регистрам контроля и статуса](../../Other/CSR.md). +- [Различия между блокирующими и неблокирующими присваиваниями](../../Basic%20Verilog%20structures/Assignments.md). ## Ход работы @@ -19,8 +19,8 @@ 2. Изучить [описание сигналов декодера инструкций](#описание-сигналов-декодера-инструкций). 3. Изучить [набор поддерживаемых инструкций **RISC-V** и способы их кодирования](#набор-поддерживаемых-инструкций-risc-v-и-способы-их-кодирования) 4. Изучить конструкции **SystemVerilog**, с помощью которых будет описан декодер ([#инструменты](#инструменты)) -5. Реализовать на языке **SystemVerilog** модуль декодера инструкций – **decoder_riscv** ([#задание](#задание)) -6. Верифицировать разработанное устройство с помощью предлагаемого **testbench** (в том же [#задании](#задание)) +5. Реализовать на языке **SystemVerilog** декодер инструкций ([#задание](#задание)) +6. Проверить с помощью верификационного окружения корректность его работы. ## Предлагаемая микроархитектура процессора RISC-V @@ -88,7 +88,7 @@ _Таблица 1. Описание портов декодера инструк ### Сигналы кода операции -В данный класс будут входить сигналы, которые сообщают отдельному функциональному блоку о том, какую из операций он должен выполнить. Таких блока два: **АЛУ** и модуль **регистров контроля и статуса**. АЛУ может выполнять одну из 16 операций, представленных в ЛР№2, для выбора которой и нужен подобный сигнал. Вы еще не знакомы с появившимся в микроархитектуре модулем регистров контроля и статуса, однако на текущий момент нужно лишь понимать, что он тоже может выполнять одну из нескольких операций и что для этого ему нужен специальный сигнал. +В данный класс будут входить сигналы, которые сообщают отдельному функциональному блоку о том, какую из операций он должен выполнить. Таких блока два: **АЛУ** и модуль **регистров контроля и статуса**. АЛУ может выполнять одну из 16 операций, представленных в ЛР№2, для выбора которой и нужен подобный сигнал. Вы ещё не знакомы с появившимся в микроархитектуре модулем регистров контроля и статуса, однако на текущий момент нужно лишь понимать, что он тоже может выполнять одну из нескольких операций и что для этого ему нужен специальный сигнал. Таким образом, в класс сигналов кода операции входят: @@ -344,17 +344,17 @@ _Таблица 7. Описание портов дешифратора кома Разница с реализацией мультиплексора в том, что в этом случае справа от знака равно всегда стоит константа. Получается это такой способ описать таблицу истинности. В такой код легко вносить правки и искать интересующие фрагменты. -Рассмотрим пример ниже. Внутри конструкции `always_comb`, перед конструкцией `case` указываются значения по-умолчанию. Благодаря этому пропадает необходимость указывать все сигналы внутри каждого обработчика `case`, достаточно указать только те, что имеют значение отличное от значения по-умолчанию. Представленный пример реализует комбинационную схему, которая при `control_signal== 4'b1100` будет выставлять сигнал `c == 1'b0`, то есть отличное, от значения по-умолчанию. Сигнал `a` никак не меняется, поэтому он не указан в соответствующем обработчике. Если сигнал `size == 1'b0`, то `b` будет равен 1, а `d` равен 0. Если сигнал `size == 1'b1`, то наоборот – `b` будет равен 0, а `d` равен 1. +Рассмотрим пример ниже. Внутри конструкции `always_comb`, перед конструкцией `case` указываются значения по умолчанию. Благодаря этому пропадает необходимость указывать все сигналы внутри каждого обработчика `case`, достаточно указать только те, что имеют значение отличное от значения по умолчанию. Представленный пример реализует комбинационную схему, которая при `control_signal== 4'b1100` будет выставлять сигнал `c == 1'b0`, то есть отличное, от значения по умолчанию. Сигнал `a` никак не меняется, поэтому он не указан в соответствующем обработчике. Если сигнал `size == 1'b0`, то `b` будет равен 1, а `d` равен 0. Если сигнал `size == 1'b1`, то наоборот – `b` будет равен 0, а `d` равен 1. -```SystemVerilog +```Verilog module example ( - input logic [3:0] control_signal; - input logic sub_signal; - output logic a, b, c, d; + input logic [3:0] control_signal, + input logic sub_signal, + output logic a, b, c, d ); parameter logic [3:0] SOME_PARAM = 4'b1100; always_comb begin - a = 1'b0; // значения по-умолчанию + a = 1'b0; // значения по умолчанию b = 1'b0; // обратите внимание, что в блоке c = 1'b1; // always_comb используется оператор d = 1'b0; // блокирующего присваивания @@ -380,7 +380,7 @@ module example ( endmodule ``` -Имейте в виду, что значения по-умолчанию, описанные в начале блока `always_comb` можно использовать таким образом при помощи **блокирующих присваиваний** (которые следует использовать только в комбинационных блоках). +Имейте в виду, что значения по умолчанию, описанные в начале блока `always_comb` можно использовать таким образом при помощи **блокирующих присваиваний** (которые следует использовать только в комбинационных блоках). Кроме того, использование вложенных блоков `case` обосновано только в ситуации создания блока декодера (т.е. в случаях, когда справа от всех присваиваний будут использованы константы, а не другие сигналы). В случае описания мультиплексора, вложенные блоки `case` могут быть синтезированы в каскад мультиплексоров, что негативно скажется на временных характеристиках схемы. @@ -388,8 +388,8 @@ endmodule Необходимо реализовать на языке **SystemVerilog** модуль декодера инструкций однотактного процессора RISC-V в соответствии с предложенной микроархитектурой. Далее приводится прототип разрабатываемого модуля. -```SystemVerilog -module decoder_riscv ( +```Verilog +module decoder ( input logic [31:0] fetched_instr_i, output logic [1:0] a_sel_o, output logic [2:0] b_sel_o, @@ -416,27 +416,21 @@ endmodule ## Порядок выполнения задания -1. Внимательно ознакомьтесь с выходными сигналами декодера инструкций и тем, за что они отвечают, а также типами команд. В случае возникновения вопросов, проконсультируйтесь с преподавателем. -2. Реализуйте модуль `decoder_riscv`. Для этого: - 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `decoder_riscv.sv`. - 2. Опишите в нем модуль декодера инструкций с таким же именем и портами, как указано в [задании](#задание). - 1. Для удобства дальнейшего описания модуля, рекомендуется сперва создать сигналы `opcode`, `func3`, `func7` и присвоить им соответствующие биты входного сигнала инструкции. - 2. При описании модуля вы можете воспользоваться параметрами, объявленными **пакетах** `decoder_pkg`, `csr_pkg` и `alu_opcodes_pkg`, описанных в файлах [decoder_pkg.sv](decoder_pkg.sv), [csr_pkg.sv](csr_pkg.sv) и [alu_opcodes_pkg.sv](alu_opcodes_pkg.sv) соответственно. - 3. Модуль может быть описан множеством способов: каждый выходной сигнал может быть описан через собственную комбинационную логику в отдельном блоке `case`, однако проще всего будет описать все сигналы через вложенные `case` внутри одного блока `always_comb`. - 4. Внутри блока `always_comb` до начала блока `case` можно указать базовые значения для всех выходных сигналов. Это не то же самое, что вариант `default` в блоке `case`. Здесь вы можете описать состояния, которые будут использованы чаще всего, и в этом случае, присваивание сигналу будет выполняться только в том месте, где появится инструкция, требующая значение этого сигнала, отличное от базового. - 5. Далее вы можете описать базовый блок `case`, где будет определен тип операции по ее коду. - 6. Определив тип операции, вы сможете определить какая конкретно операция по полям `func3` и `func7` (если данный тип имеет такие поля). - 7. Не забывайте, что в случае, если на каком-то из этапов (определения типа, или определения конкретной операции) вам приходит непредусмотренное ISA значение какого-либо поля, необходимо выставить сигнал `illegal_instr_o`. - 8. В случае некорректной инструкции, вы должны гарантировать, что не произойдет условный/безусловный переход, а во внешнюю память, регистровый файл, а также регистры контроля и статуса ничего не запишется. Не важно, что будет выполняться на АЛУ, не важно какие данные будут выбраны на мультиплексоре источника записи. Важно чтобы не произошел сам факт записи в любое из устройств (подумайте какие значения для каких сигналов необходимо для этого выставить). -3. После описания модуля его необходимо проверить с помощью тестового окружения. - 1. Тестовое окружение находится [`здесь`](tb_decoder_riscv.sv). - 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 3. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня. - 4. **Во время симуляции, вы должны прожать "Run All" и убедиться, что в логе есть сообщение о завершении теста!** - 5. Вполне возможно, что после первого запуска вы столкнетесь с сообщениями о множестве ошибок. Вам необходимо [исследовать](../../Vivado%20Basics/Debug%20manual.md) эти ошибки на временной диаграмме и исправить их в вашем модуле. -4. Данная лабораторная работа не предполагает проверки в ПЛИС - -## Источники - -1. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf) -2. [The RISC-V Instruction Set Manual Volume II: Privileged Architecture](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf) +1. Внимательно ознакомьтесь с выходными сигналами декодера инструкций и тем, как они управляют функциональными блоками процессорного ядра, представленного на _рис. 1_, а также типами команд. В случае возникновения вопросов, проконсультируйтесь с преподавателем. +2. Добавьте в `Design Sources` проекта файл [`alu_opcodes_pkg.sv`](alu_opcodes_pkg.sv) (если тот ещё не был добавлен в ходе выполнения ЛР№2), а также файлы [`csr_pkg.sv`](csr_pkg.sv) и [`decoder_pkg.sv`](decoder_pkg.sv). Эти файлы содержат параметры, которые будет удобно использовать при описании декодера. +3. Опишите модуль декодера инструкций с таким же именем и портами, как указано в задании. + 1. Для удобства дальнейшего описания модуля, рекомендуется сперва создать сигналы `opcode`, `func3`, `func7` и присвоить им соответствующие биты входного сигнала инструкции. + 2. Модуль может быть описан множеством способов: каждый выходной сигнал может быть описан через собственную комбинационную логику в отдельном блоке `case`, однако проще всего будет описать все сигналы через вложенные `case` внутри одного блока `always_comb`. + 3. Внутри блока `always_comb` до начала блока `case` можно указать базовые значения для всех выходных сигналов. Это не то же самое, что вариант `default` в блоке `case`. Здесь вы можете описать состояния, которые будут использованы чаще всего, и в этом случае, присваивание сигналу будет выполняться только в том месте, где появится инструкция, требующая значение этого сигнала, отличное от базового. + 4. Далее вы можете описать базовый блок `case`, где будет определен тип операции по ее коду. + 5. Определив тип операции, вы сможете определить какая конкретно операция по полям `func3` и `func7` (если данный тип имеет такие поля). + 6. Не забывайте, что в случае, если на каком-то из этапов (определения типа, или определения конкретной операции) вам приходит непредусмотренное ISA значение какого-либо поля, необходимо выставить сигнал `illegal_instr_o`. + 7. В случае некорректной инструкции, вы должны гарантировать, что не произойдет условный/безусловный переход, а во внешнюю память, регистровый файл, а также регистры контроля и статуса ничего не запишется. Не важно, что будет выполняться на АЛУ, не важно какие данные будут выбраны на мультиплексоре источника записи. Важно чтобы не произошел сам факт записи в любое из устройств (подумайте какие значения для каких сигналов необходимо для этого выставить). +4. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_05.tb_decoder.sv`](lab_05.tb_decoder.sv). Вполне возможно, что после первого запуска вы столкнётесь с сообщениями о множестве ошибок. Вам необходимо [исследовать](../../Vivado%20Basics/05.%20Bug%20hunting.md) эти ошибки на временной диаграмме и исправить их в вашем модуле. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +5. Данная лабораторная работа не предполагает проверки в ПЛИС + +## Список источников + +1. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf) +2. [The RISC-V Instruction Set Manual Volume II: Privileged Architecture](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.pdf) diff --git a/Labs/05. Main decoder/tb_decoder_riscv.sv b/Labs/05. Main decoder/lab_05.tb_decoder.sv similarity index 99% rename from Labs/05. Main decoder/tb_decoder_riscv.sv rename to Labs/05. Main decoder/lab_05.tb_decoder.sv index 2e9b260f..b7a109c8 100644 --- a/Labs/05. Main decoder/tb_decoder_riscv.sv +++ b/Labs/05. Main decoder/lab_05.tb_decoder.sv @@ -9,7 +9,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_decoder_riscv(); +module lab_05_tb_decoder(); import decoder_pkg::*; typedef class riscv_instr; @@ -130,8 +130,6 @@ module tb_decoder_riscv(); initial begin $display("Test has been started"); - $display( "\n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop(); - valid_instrs_direct_test(); valid_instrs_random_test(); illegal_instr_direct_test(); @@ -403,9 +401,9 @@ module tb_decoder_riscv(); logic grm_mret_o; - decoder_riscv DUT(.*); + decoder DUT(.*); - decoder_riscv_ref GRM( + decoder_ref GRM( .fetched_instr_i (fetched_instr_i ), .a_sel_o (grm_a_sel_o ), .b_sel_o (grm_b_sel_o ), @@ -1057,7 +1055,7 @@ module jalr_table (gis_rlaj, edocpo_6, edocpo_5, edocpo_4, edocpo_3, edocpo_2); endcase endmodule -module decoder_riscv_ref ( +module decoder_ref ( input logic [31:0] fetched_instr_i, output logic [1:0] a_sel_o, output logic [2:0] b_sel_o, diff --git a/Labs/06. Main memory/README.md b/Labs/06. Main memory/README.md index 29d6688f..e7cf2cec 100644 --- a/Labs/06. Main memory/README.md +++ b/Labs/06. Main memory/README.md @@ -6,23 +6,23 @@ Описать память данных, с побайтовой адресацией. -## Материал для подготовки к лабораторной работе +## Материалы для подготовки к лабораторной работе -Для успешного выполнения лабораторной работы, вам необходимо использовать навыки, полученные при написании [лабораторной работы №3](../03.%20Register%20file%20and%20memory/) "Регистровый файл и память инструкций"; +Для успешного выполнения лабораторной работы, вам необходимо использовать навыки, полученные при написании [ЛР№3](../03.%20Register%20file%20and%20memory/) "Регистровый файл и память инструкций"; ## Теория -В задании по реализации памяти инструкций [лабораторной работы №3](../03.%20Register%20file%20and%20memory/) байтовая адресация была описана следующим образом: +В задании по реализации памяти инструкций [ЛР№3](../03.%20Register%20file%20and%20memory/) байтовая адресация была описана следующим образом: -> Байтовая адресация означает, что процессор способен обращаться к отдельным байтам в памяти (за каждым байтом памяти закреплен свой индивидуальный адрес). +> Байтовая адресация означает, что процессор способен обращаться к отдельным байтам в памяти (за каждым байтом памяти закреплён свой индивидуальный адрес). -Данное описание было дано не совсем корректным образом, чтобы в третьей лабораторной работе было более четкое понимание задания. В чем заключается некорректность? Процессор должен быть способен не только **обращаться** к отдельным байтам в памяти, но и **обновлять** в памяти любой отдельный байт, а также **считывать** отдельные байты. +Данное описание было дано не совсем корректным образом, чтобы в третьей лабораторной работе было более чёткое понимание задания. В чём заключается некорректность? Процессор должен быть способен не только **обращаться** к отдельным байтам в памяти, но и **обновлять** в памяти любой отдельный байт, а также **считывать** отдельные байты. Вопрос считывания отдельного байта будет решаться специальным модулем **загрузки и сохранения**. Памяти данных при этом будет достаточно возвращать всё слово, содержащее запрашиваемый байт как это уже было сделано памяти инструкций. Нас интересует возможность памяти обновлять любой из байт в слове. Подобный функционал часто используется при реализации памяти и в системных интерфейсах, например AXI4 или APB. Для этого используется специальный сигнал, который называется `byte enable`. Разрядность этого сигнала равна числу байт в шине данных (в нашем случае разрядность `byte enable` составляет 4). Вы можете представить этот сигнал, как 4 провода, каждый из которых является сигналом разрешения записи для отдельной памяти с шириной данных в 1 байт. -Давайте разберемся как это будет работать. Допустим, мы хотим записать значение `0xA5` по адресу `0x6`. Поскольку мы работаем с байтовой адресацией, а ячейки памяти 32-битные — как и при реализации памяти инструкций, пришедший адрес необходимо будет разделить на 4 (см. _рис. 1_). В итоге мы получим указатель на первую 32-битную ячейку памяти (`6 / 4 = 1`). Однако, чтобы пришедшие данные были в итоге записаны не в нулевой байт первого слова (четвертый байт памяти), а во второй, мы будем использовать сигнал `byte enable`, второй бит которого будет равен `1`. Это значит, что лучше разделить запись в отдельные байты памяти и для каждого байта проверять отдельно соответствующий бит `byte enable`, независимо от остальных. +Давайте разберёмся как это будет работать. Допустим, мы хотим записать значение `0xA5` по адресу `0x6`. Поскольку мы работаем с байтовой адресацией, а ячейки памяти 32-битные — как и при реализации памяти инструкций, пришедший адрес необходимо будет разделить на 4 (см. _рис. 1_). В итоге мы получим указатель на первую 32-битную ячейку памяти (`6 / 4 = 1`). Однако, чтобы пришедшие данные были в итоге записаны не в нулевой байт первого слова (четвёртый байт памяти), а во второй, мы будем использовать сигнал `byte enable`, второй бит которого будет равен `1`. Это значит, что лучше разделить запись в отдельные байты памяти и для каждого байта проверять отдельно соответствующий бит `byte enable`, независимо от остальных. ![../../.pic/Labs/lab_06_main_memory/fig_01.png](../../.pic/Labs/lab_06_main_memory/fig_01.png) @@ -30,7 +30,7 @@ _Рисунок 1. Связь адреса байта с индексом сло Чтобы данные остальных байт не были испорчены, при описании памяти на SystemVerilog нужно разделить запись в отдельные байты. Для того, чтобы получить доступ к отдельным диапазонам бит ячейки памяти, после указания индекса ячейки необходимо указать диапазон бит, к которым вы хотите получить доступ. К примеру, чтобы получить доступ к битам с 5-го по 3-ий 18-ой ячейки памяти, необходимо использовать следующую запись: -```SystemVerilog +```Verilog mem[18][5:3]; ``` @@ -51,7 +51,7 @@ mem[18][5:3]; Прототип модуля следующий: -```SystemVerilog +```Verilog module data_mem import memory_pkg::DATA_MEM_SIZE_BYTES; import memory_pkg::DATA_MEM_SIZE_WORDS; @@ -99,28 +99,24 @@ _Рисунок 2. Операции запросов на чтение._ Если `mem_req_i == 1` и `write_enable_i == 1`, то происходит запрос на запись в память. В этом случае, необходимо записать значение `write_data_i` в ячейку по, на которую указывает `addr_i`. Во всех других случаях (любой из сигналов `mem_req_i`, `write_enable_i` равен нулю), запись в память не производится. Запись необходимо производить только в те байты указанной ячейки, которым соответствуют биты сигнала `byte_enable_i`, равные 1. -На _рис. 3_ показан пример записей по различным адресам. Т.к. деление на 4 любого из приведенных на _рис. 3_ адресов дает результат 2, на рисунке показано только содержимое второй 32-битной ячейки памяти и то, как оно менялось в зависимости от комбинации сигналов `write_data_i` и `byte_enable_i`. +На _рис. 3_ показан пример записей по различным адресам. Т.к. деление на 4 любого из приведенных на _рис. 3_ адресов даёт результат 2, на рисунке показано только содержимое второй 32-битной ячейки памяти и то, как оно менялось в зависимости от комбинации сигналов `write_data_i` и `byte_enable_i`. ![../../.pic/Labs/lab_06_main_memory/fig_03.wavedrom.svg](../../.pic/Labs/lab_06_main_memory/fig_03.wavedrom.svg) _Рисунок 3. Операции запросов на запись._ -Выход `ready_o` в данном модуле должен всегда быть равен 1, поскольку данные всегда будут выдаваться на следующий такт. В реальности, обращение в память может занимать сотни тактов процессора, причем их число бывает недетерминированным (нельзя заранее предсказать сколько тактов займет очередной запрос в память). Именно поэтому стандартные интерфейсы обычно используют такие сигналы как `ready` или `valid`, позволяющие синхронизировать разные блоки системы. Сигнал `ready_o` в нашем интерфейсе используется сигнала о задержке в выдаче данных. В случае, если устройству нужно больше одного такта, чтобы выдать данные, он устанавливает на данный сигнал значение `0` до тех пор, пока данные не будут готовы. +Выход `ready_o` в данном модуле должен всегда быть равен 1, поскольку данные всегда будут выдаваться на следующий такт. В реальности, обращение в память может занимать сотни тактов процессора, причём их число бывает недетерминированным (нельзя заранее предсказать сколько тактов займёт очередной запрос в память). Именно поэтому стандартные интерфейсы обычно используют такие сигналы как `ready` или `valid`, позволяющие синхронизировать разные блоки системы. Сигнал `ready_o` в нашем интерфейсе используется сигнала о задержке в выдаче данных. В случае, если устройству нужно больше одного такта, чтобы выдать данные, он устанавливает на данный сигнал значение `0` до тех пор, пока данные не будут готовы. ## Порядок выполнения работы -1. Внимательно ознакомьтесь с заданием. В случае возникновения вопросов, проконсультируйтесь с преподавателем. -2. Реализуйте память данных. Для этого: - 1. В `Design Sources` проекта создайте `SystemVerilog`-файл `data_mem.sv`. - 2. Опишите в нем модуль памяти данных с таким же именем и портами, как указано в задании. - 1. Описание модуля будет схожим с описанием модуля памяти инструкций, однако порт чтения в этот раз будет **синхронным** (запись в него будет происходить в блоке `always_ff`). Количество ячеек памяти данных определяется параметром `DATA_MEM_SIZE_WORDS`, определенным в `memory_pkg`. Кроме того, необходимо будет описать логику записи данных в память. - 2. Запись в ячейки памяти описывается подобно записи данных в [регистры](../../Basic%20Verilog%20structures/Registers.md), только при этом, происходит доступ к конкретной ячейке памяти с помощью входа `addr_i`. - 3. Перед тем как обратиться к ячейке памяти, значение с `addr_i` необходимо преобразовать по аналогии с памятью инструкций. - 4. Обратите внимание что работа с памятью должна осуществляться только когда сигнал `mem_req_i == 1`. В противном случае запись не должна производиться, а на шине `read_data_o` должен оставаться результат предыдущего чтения. - 5. При этом запись должна вестись только в те байты выбранной ячейки памяти, которым соответствуют биты сигнала `byte_enable_i`, выставленные в `1`. - 6. У памяти есть дополнительный выход `ready_o`, который всегда равен единице. - 3. После описания модуля его необходимо проверить с помощью тестового окружения. - 1. Тестовое окружение находится [`здесь`](tb_data_mem.sv). - 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_data_mem`). - 4. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** +1. Опишите память данных с таким же именем и портами, как указано в задании. + 1. Обратите внимание, что имя памяти (не название модуля, а имя массива регистров внутри модуля) должно быть **ram**. Такое имя необходимо для корректной работы верификационного окружения + 2. Описание модуля будет схожим с описанием модуля памяти инструкций, однако порт чтения в этот раз будет **синхронным** (запись в него будет происходить в блоке `always_ff`). Количество ячеек памяти данных определяется параметром `DATA_MEM_SIZE_WORDS`, определенным в `memory_pkg`. Кроме того, необходимо будет описать логику записи данных в память. + 3. Запись в ячейки памяти описывается подобно записи данных в [регистры](../../Basic%20Verilog%20structures/Registers.md), только при этом, происходит доступ к конкретной ячейке памяти с помощью входа `addr_i`. + 4. Перед тем как обратиться к ячейке памяти, значение с `addr_i` необходимо преобразовать по аналогии с памятью инструкций. + 5. Обратите внимание что работа с памятью должна осуществляться только когда сигнал `mem_req_i == 1`. В противном случае запись не должна производиться, а на шине `read_data_o` должен оставаться результат предыдущего чтения. + 6. При этом запись должна вестись только в те байты выбранной ячейки памяти, которым соответствуют биты сигнала `byte_enable_i`, выставленные в `1`. + 7. У памяти есть дополнительный выход `ready_o`, который всегда равен единице. +2. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_06.tb_data_mem.sv`](lab_06.tb_data_mem.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +3. Данная лабораторная работа не предполагает проверки в ПЛИС. diff --git a/Labs/06. Main memory/tb_data_mem.sv b/Labs/06. Main memory/lab_06.tb_data_mem.sv similarity index 99% rename from Labs/06. Main memory/tb_data_mem.sv rename to Labs/06. Main memory/lab_06.tb_data_mem.sv index 184a430b..c0cc9ede 100644 --- a/Labs/06. Main memory/tb_data_mem.sv +++ b/Labs/06. Main memory/lab_06.tb_data_mem.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_data_mem; +module lab_06_tb_data_mem; // Входы import memory_pkg::DATA_MEM_SIZE_WORDS; import memory_pkg::DATA_MEM_SIZE_BYTES; diff --git a/Labs/07. Datapath/README.md b/Labs/07. Datapath/README.md index 78cddd74..eee1bb2f 100644 --- a/Labs/07. Datapath/README.md +++ b/Labs/07. Datapath/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 7 "Тракт данных" +# Лабораторная работа №7 "Тракт данных" Микроархитектуру можно разделить на две части: тракт данных и устройство управления. По тракту данных перемещаются данные (из памяти инструкций, регистрового файла, АЛУ, памяти данных, мультиплексоров), а устройство управления (в нашем случае — декодер инструкций) получает текущую инструкцию из тракта и в ответ говорит ему как именно её выполнить, то есть управляет тем, как эти данные будут через проходить тракт данных. @@ -15,13 +15,12 @@ ## Микроархитектура RISC-V -### riscv_core +### processor_core -Рассмотрим микроархитектуру процессорного ядра `riscv_core`. Данный модуль обладает следующим прототипом и микроархитектурой: - -```SystemVerilog -module riscv_core ( +Рассмотрим микроархитектуру процессорного ядра `processor_core`. Данный модуль обладает следующим прототипом и микроархитектурой: +```Verilog +module processor_core ( input logic clk_i, input logic rst_i, @@ -67,16 +66,16 @@ _Рисунок 1. Микроархитектура ядра процессор Константы `B` и `J` используются для условного и безусловного перехода (в киберкобре для этого использовалась одна константа `offset`). -Программный счетчик (`PC`) теперь также изменяется более сложным образом. Поскольку появился еще один вид безусловного перехода (`jalr`), программный счетчик может не просто увеличиться на значение константы из инструкции, но и получить совершенно новое значение в виде суммы константы и значения из регистрового файла (см. на самый левый мультиплексор схемы). Обратите внимание, что младший бит этой суммы должен быть обнулен — таково требование спецификации. +Программный счётчик (`PC`) теперь также изменяется более сложным образом. Поскольку появился еще один вид безусловного перехода (`jalr`), программный счётчик может не просто увеличиться на значение константы из инструкции, но и получить совершенно новое значение в виде суммы константы и значения из регистрового файла (см. на самый левый мультиплексор _рис. 1_). Обратите внимание, что младший бит этой суммы должен быть обнулен — таково требование спецификации [[1](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf), стр. 28]. -Поскольку обращение во внешнюю память требует времени, необходимо приостанавливать программный счетчик, чтобы до конца обращения в память не начались исполняться последующие инструкции. Для этого у программного счетчика появился управляющий сигнал `stall_i`. Программный счетчик может меняться только когда этот сигнал равен нулю (иными словами, инверсия этого сигнала является сигналом `enable` для регистра `PC`). +Поскольку обращение во внешнюю память требует времени, необходимо приостанавливать программный счётчик, чтобы до конца обращения в память не начались исполняться последующие инструкции. Для этого у программного счётчика появился управляющий сигнал `stall_i`. Программный счётчик может меняться только когда этот сигнал равен нулю (иными словами, инверсия этого сигнала является сигналом `enable` для регистра `PC`). -### riscv_unit +### processor_system -После реализации процессорного ядра, к нему необходимо подключить память. Это происходит в модуле `riscv_unit`. +После реализации процессорного ядра, к нему необходимо подключить память. Это происходит в модуле `processor_system`. -```SystemVerilog -module riscv_unit( +```Verilog +module processor_system( input logic clk_i, input logic rst_i ); @@ -86,39 +85,14 @@ endmodule ![../../.pic/Labs/lab_07_dp/fig_02.drawio.svg](../../.pic/Labs/lab_07_dp/fig_02.drawio.svg) -_Рисунок 2. Микроархитектура процессора._ +_Рисунок 2. Микроархитектура процессорной системы._ -Обратите внимание на регистр `stall`. Этот регистр и будет управлять разрешением на запись в программный счетчик `PC`. Поскольку мы используем блочную память, расположенную прямо в ПЛИС, доступ к ней осуществляется за 1 такт, а значит, что при обращении в память, нам необходимо "отключить" программный счетчик ровно на 1 такт. Если бы использовалась действительно "внешняя" память (например чип DDR3), то вместо этого регистра появилась бы другая логика, выставляющая на вход ядра `stall_i` единицу пока идет обращение в память. +Обратите внимание на регистр `stall`. Этот регистр и будет управлять разрешением на запись в программный счётчик `PC`. Поскольку мы используем блочную память, расположенную прямо в ПЛИС, доступ к ней осуществляется за 1 такт, а значит, что при обращении в память, нам необходимо "отключить" программный счётчик ровно на 1 такт. Если бы использовалась действительно "внешняя" память (например чип DDR3), то вместо этого регистра появилась бы другая логика, выставляющая на вход ядра `stall_i` единицу пока идет обращение в память. ## Задание -Реализовать ядро процессора `riscv_core` архитектуры RISC-V по предложенной микроархитектуре. Подключить к нему память инструкций и память данных в модуле `riscv_unit`. Проверить работу процессора с помощью программы, написанной на ассемблере RISC-V по индивидуальному заданию, которое использовалось для написания программы для процессора архитектуры CYBERcobra. - - Напишем простую программу, которая использует все типы инструкций для проверки нашего процессора. Сначала напишем программу на ассемблере: ```assembly @@ -191,36 +165,24 @@ _Листинг 2. Программа из Листинга 1, представ ## Порядок выполнения задания -1. Внимательно ознакомьтесь микроархитектурной реализацией. В случае возникновения вопросов, проконсультируйтесь с преподавателем. -2. Реализуйте модуль `riscv_core`. Для этого: - 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `riscv_core.sv`. - 2. Опишите в нем модуль процессор `riscv_core` с таким же именем и портами, как указано в [задании](#задание). - 1. Процесс реализации модуля очень похож на процесс описания модуля cybercobra, однако теперь появляется: - 1. декодер - 2. дополнительные мультиплексоры и знакорасширители. - 2. Сперва рекомендуется создать все провода, которые будут подключены к входам и выходам каждого модуля на схеме. - 3. Затем необходимо создать экземпляры модулей. - 4. Также необходимо создать 32-разрядные константы I, U, S, B и J-типа и программный счетчик. - 5. После необходимо описать логику, управляющую созданными в п. 2.1 проводами. - 6. В конце останется описать логику работы программного счетчика. - 3. Создайте в проекте новый `SystemVerilog`-файл `riscv_unit.sv` и опишите в нем модуль `riscv_unit`, объединяющий ядро процессора (`riscv_core`) с памятями инструкция и данных. - 1. **При создании объекта модуля `riscv_core` в модуле `riscv_unit` вы должны использовать имя сущности `core` (т.е. создать объект в виде: `riscv_core core(...`)**. -3. После описания модуля его необходимо проверить с помощью тестового окружения. - 1. Тестовое окружение находится [`здесь`](tb_riscv_unit.sv). - 2. Программа, которой необходимо проинициализировать память инструкций находится в файле [`program.mem`](program.mem). - 3. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 4. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня. - 5. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** - 6. Как и в случае с проверкой процессора архитектуры CYBERcobra, вам не будет сказано пройден тест или нет. Вы должны сами, такт за тактом проверить что процессор правильно выполняет описанные в _Листинге 1_ инструкции. Для этого, необходимо сперва самостоятельно рассчитать что именно должна сделать данная инструкция, а потом проверить что процессор сделал именно это. - 7. Вполне возможно, что после первого запуска вы столкнетесь с сообщениями о множестве ошибок. Вам необходимо [исследовать](../../Vivado%20Basics/Debug%20manual.md) эти ошибки на временной диаграмме и исправить их в вашем модуле. -4. Проверьте работоспособность вашей цифровой схемы в ПЛИС. Для этого: - 1. Добавьте файлы из папки [`board files`](https://github.com/MPSU/APS/tree/master/Labs/07.%20Datapath/board%20files) в проект. - 1. Файл [nexys_riscv_unit.sv](https://github.com/MPSU/APS/tree/master/Labs/07.%20Datapath/board%20files/nexys_riscv_unit.sv) необходимо добавить в `Design Sources` проекта. - 2. Файл [nexys_a7_100t.xdc](https://github.com/MPSU/APS/tree/master/Labs/07.%20Datapath/board%20files/nexys_a7_100t.xdc) необходимо добавить в `Constraints` проекта. В случае, если вы уже добавляли одноименный файл в рамках предыдущих лабораторных работ, его содержимое необходимо заменить содержимым нового файла. - 2. Выберите `nexys_riscv_unit` в качестве модуля верхнего уровня (`top-level`). - 3. Выполните генерацию битстрима и сконфигурируйте ПЛИС. Для этого воспользуйтесь [следующей инструкцией](../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). - 4. Описание логики работы модуля верхнего уровня и связи периферии ПЛИС с реализованным модулем находится в папке [`board files`](https://github.com/MPSU/APS/tree/master/Labs/07.%20Datapath/board%20files). - 5. После всех проверок вы можете загрузить ваше индивидуальное задание, написанное на языке ассемблера RISC-V чтобы проверить, что ваш процессор вычисляет корректный результат. +1. Внимательно ознакомьтесь микроархитектурной реализацией процессорного ядра. В случае возникновения вопросов, проконсультируйтесь с преподавателем. +2. Замените файл `program.mem` в `Design Sources` проекта новым файлом [program.mem](program.mem), приложенном в данной лабораторной работе. Данный файл содержит программу из _листинга 1_. +3. Опишите модуль процессорного ядра с таким же именем и портами, как указано в задании. + 1. Процесс реализации модуля очень похож на процесс описания модуля cybercobra, однако теперь появляется: + 1. декодер + 2. дополнительные мультиплексоры и знакорасширители. + 2. Сперва рекомендуется создать все провода, которые будут подключены к входам и выходам каждого модуля на схеме. + 3. Затем необходимо создать экземпляры модулей. + 4. Также необходимо создать 32-разрядные константы I, U, S, B и J-типа и программный счётчик. + 5. После необходимо описать логику, управляющую созданными в п. 3.2 проводами. + 6. В конце останется описать логику работы программного счётчика. +4. Опишите модуль процессорной системы, объединяющий ядро процессора (`processor_core`) с памятями инструкция и данных. + 1. Опишите модуль с таким же именем и портами, как указано в задании. + 2. **При создании объекта модуля `processor_core` в модуле `processor_system` вы должны использовать имя сущности `core` (т.е. создать объект в виде: `processor_core core(...`)**. +5. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_07.tb_processor_system.sv`](lab_07.tb_processor_system.sv). + 1. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня в `Simulation Sources`. + 2. Как и в случае с проверкой процессора архитектуры CYBERcobra, вам не будет сказано пройден тест или нет. Вам необходимо самостоятельно, такт за тактом проверить что процессор правильно выполняет описанные в _Листинге 1_ инструкции (см. порядок выполнения задания ЛР№4). Для этого, необходимо сперва самостоятельно рассчитать что именно должна сделать данная инструкция, а потом проверить что процессор сделал именно это. +6. Проверьте работоспособность вашей цифровой схемы в ПЛИС. --- @@ -231,3 +193,7 @@ _Листинг 2. Программа из Листинга 1, представ >Я способен(на) на всё! Я сам(а) полностью, с нуля, сделал(а) процессор с архитектурой RISC-V! Что? Не знаешь, что такое архитектура? Пф, щегол! Подрастешь – узнаешь + +## Список источников + +1. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf). diff --git a/Labs/07. Datapath/board files/README.md b/Labs/07. Datapath/board files/README.md index 80d4cbf5..c3ca58b9 100644 --- a/Labs/07. Datapath/board files/README.md +++ b/Labs/07. Datapath/board files/README.md @@ -2,7 +2,7 @@ После того, как вы проверили на моделировании дизайн, вам необходимо проверить его работу на прототипе в ПЛИС. -Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/How%20to%20program%20an%20fpga%20board.md). +Инструкция по реализации прототипа описана [здесь](../../../Vivado%20Basics/07.%20Program%20and%20debug.md). На _рис. 1_ представлена схема прототипа в ПЛИС. diff --git a/Labs/07. Datapath/tb_riscv_unit.sv b/Labs/07. Datapath/lab_07.tb_processor_system.sv similarity index 71% rename from Labs/07. Datapath/tb_riscv_unit.sv rename to Labs/07. Datapath/lab_07.tb_processor_system.sv index 0f2a440b..89327e70 100644 --- a/Labs/07. Datapath/tb_riscv_unit.sv +++ b/Labs/07. Datapath/lab_07.tb_processor_system.sv @@ -8,12 +8,12 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_riscv_unit(); +module lab_07_tb_processor_system(); reg clk; reg rst; - riscv_unit unit( + processor_system system( .clk_i(clk), .rst_i(rst) ); @@ -21,7 +21,7 @@ module tb_riscv_unit(); initial clk = 0; always #10 clk = ~clk; initial begin - $display( "\nStart test: \n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop(); + $display( "\nTest has been started."); rst = 1; #40; rst = 0; @@ -31,12 +31,12 @@ module tb_riscv_unit(); end stall_seq: assert property ( - @(posedge unit.core.clk_i) disable iff ( unit.core.rst_i ) - unit.core.mem_req_o |-> (unit.core.stall_i || $past(unit.core.stall_i)) + @(posedge system.core.clk_i) disable iff ( system.core.rst_i ) + system.core.mem_req_o |-> (system.core.stall_i || $past(system.core.stall_i)) )else $error("\nincorrect implementation of stall signal\n"); stall_seq_fall: assert property ( - @(posedge unit.core.clk_i) disable iff ( unit.core.rst_i ) - (unit.core.stall_i) |=> !unit.core.stall_i + @(posedge system.core.clk_i) disable iff ( system.core.rst_i ) + (system.core.stall_i) |=> !system.core.stall_i )else $error("\nstall must fall exact one cycle after rising\n"); endmodule diff --git a/Labs/08. Load-store unit/README.md b/Labs/08. Load-store unit/README.md index 5e6d758c..29e82f7c 100644 --- a/Labs/08. Load-store unit/README.md +++ b/Labs/08. Load-store unit/README.md @@ -1,6 +1,6 @@ -# Лабораторная работа 8 "Блок загрузки и сохранения" +# Лабораторная работа №8 "Блок загрузки и сохранения" -Итогом шестой лабораторной работы стал практически завершенный процессор архитектуры RISC-V. Особенностью той реализации процессора было отсутствие поддержки инструкций `LB`, `LBU`, `SB`, `LH`, `LHU`, `SH`. Тому было две причины: +Итогом шестой лабораторной работы стал практически завершенный процессор архитектуры RISC-V. Особенностью реализации процессора было отсутствие поддержки инструкций `LB`, `LBU`, `SB`, `LH`, `LHU`, `SH`. Тому было две причины: - подключенный к памяти данных сигнал `byte_enable_i` был аппаратно зафиксирован на значении `4'b1111`, но на самом деле этим сигналом должен кто-то управлять; - необходимо подготовить считанные из памяти полуслова / байты для записи в регистровый файл. @@ -21,7 +21,7 @@ - Интерфейс процессора и блока загрузки/сохранения - Интерфейс блока загрузки/сохранения и памяти -Реализовать и проверить модуль `riscv_lsu`. +Реализовать и проверить модуль `lsu`. --- @@ -39,7 +39,7 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр Инструкции `LOAD` и `STORE` в **RV32I** поддерживают обмен 8-битными, 16-битными или 32-битными значениями, однако в самом процессоре происходит работа только с 32-битными числами, поэтому загружаемые из памяти байты и послуслова необходимо предварительно расширить до 32-битного значения. Расширять значения можно либо знаковым битом, либо нулями — в зависимости от того как должно быть интерпретировано загружаемое число: как знаковое или беззнаковое. Во время записи данных в память, они не расширяются, поскольку в отличие от регистрового файла, основная память имеет возможность обновлять отдельные байты. Таким образом, различать знаковые и беззнаковые числа необходимо только на этапе загрузки, но не сохранения. -Для выбора разрядности и формата представления числа, на вход **LSU** подается сигнал `core_size_i`, принимающий следующие значения (для удобства использования, данные значения определены в виде параметров в пакете `decoder_pkg`): +Для выбора разрядности и формата представления числа, на вход **LSU** подаётся сигнал `core_size_i`, принимающий следующие значения (для удобства использования, данные значения определены в виде параметров в пакете `decoder_pkg`): | Параметр |Значение| Пояснение | |----------|--------|-------------------------------| @@ -51,7 +51,7 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр Для операций типа `STORE` формат представления чисел не важен, для них `core_size_i` сможет принимать значение только от 0 до 2. -Выходной сигнал `core_stall_o` нужен для приостановки программного счетчика. Ранее логика этого сигнала временно находилась в модуле `riscv_unit` — теперь она займет свое законное место в модуле **LSU**. +Выходной сигнал `core_stall_o` нужен для приостановки программного счётчика. Ранее логика этого сигнала временно находилась в модуле `processor_system` — теперь она займёт своё законное место в модуле **LSU**. ### Интерфейс блока загрузки/сохранения и памяти @@ -59,11 +59,11 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр После получения запроса на чтение/запись из ядра **LSU** перенаправляет запрос в память данных, взаимодействие осуществляется следующими сигналами: -- сигнал `mem_req_o` сообщает памяти о наличии запроса в память (напрямую подключен к `core_req_i`); -- сигнал `mem_we_o` сообщает памяти о типе этого запроса (напрямую подключен к `core_we_i`): +- сигнал `mem_req_o` сообщает памяти о наличии запроса в память (напрямую подключён к `core_req_i`); +- сигнал `mem_we_o` сообщает памяти о типе этого запроса (напрямую подключён к `core_we_i`): - `mem_we_o` равен 1, если отправлен запрос на запись, - `mem_we_o` равен 0, если отправлен запрос на чтение; -- сигнал `mem_wd_o` содержит данные на запись в память. В зависимости от размера записи, данные этого сигнала будут отличаться от пришедшего сигнала `core_wd_i` и будут является результатом определенных преобразований. +- сигнал `mem_wd_o` содержит данные на запись в память. В зависимости от размера записи, данные этого сигнала будут отличаться от пришедшего сигнала `core_wd_i` и будут является результатом определённых преобразований. - сигнал `mem_rd_i` содержит считанные из памяти данные. Перед тем, как вернуть считанные данные ядру через выходной сигнал `core_rd_o`, эти данные будет необходимо подготовить. - сигнал `mem_ready_i` сообщает о готовности памяти завершить транзакцию на текущем такте. Этот сигнал используется для управления выходным сигналом `core_stall_o`. @@ -98,7 +98,7 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр > Познай как описать выходные сигналы модуля — и ты познаешь как описать сам модуль. ©Джейсон Стейтем -Реализация любого модуля сводится к реализации логики, управляющей каждым отдельным выходным сигналом посредством входных сигналов. Разберем принцип работы каждого выходного сигнала: +Реализация любого модуля сводится к реализации логики, управляющей каждым отдельным выходным сигналом посредством входных сигналов. Разберём принцип работы каждого выходного сигнала: ### mem_req_o, mem_we_o, mem_addr_o @@ -112,7 +112,7 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр Данный сигнал принимает ненулевые значения только по запросу на запись (`core_req_i == 1`, `core_we_i == 1`), во время которого происходит мультиплексирование сигнала `core_size_i`. Если `core_size_i` соответствует инструкции записи байта (`LDST_B`, 3'd0), то в сигнале `mem_be_o` бит с индексом равным значению двух младших бит адреса `core_addr_i` должен быть равен единице. -Допустим, пришел запрос на запись байта по адресу 18: +Допустим, пришёл запрос на запись байта по адресу 18: - `core_req_i == 1`, - `core_we_i == 1`, @@ -121,9 +121,9 @@ _Рисунок 1. Место LSU в микроархитектуре RISC-пр В данном случае, необходимо выставить единицу во втором (считая с нуля) бите сигнала `mem_be_o` (поскольку значение двух младших бит `core_addr_i` равно двум): `mem_be_o == 4'b0100`. -Если пришел запрос на запись полуслова (`core_size_i == LDST_H`), то в сигнале `mem_be_o` необходимо выставить в единицу либо два старших, либо два младших бита (в зависимости от `core_addr_i[1]`: если `core_addr_i[1] == 1`, то в двух старших битах, если `core_addr_i[1] == 0`, то в двух младших). +Если пришёл запрос на запись полуслова (`core_size_i == LDST_H`), то в сигнале `mem_be_o` необходимо выставить в единицу либо два старших, либо два младших бита (в зависимости от `core_addr_i[1]`: если `core_addr_i[1] == 1`, то в двух старших битах, если `core_addr_i[1] == 0`, то в двух младших). -Если пришел запрос на запись слова (`core_size_i == LDST_W`), то в сигнале `mem_be_o` необходимо выставить в единицу все биты. +Если пришёл запрос на запись слова (`core_size_i == LDST_W`), то в сигнале `mem_be_o` необходимо выставить в единицу все биты. ![../../.pic/Labs/lab_08_lsu/fig_02.wavedrom.svg](../../.pic/Labs/lab_08_lsu/fig_02.wavedrom.svg) @@ -178,9 +178,9 @@ _Рисунок 4. Временна́я диаграмма запросов на ### core_stall_o -Сигнал `core_stall_o` запрещает менять значение программного счетчика на время обращения в память. Этот сигнал должен: +Сигнал `core_stall_o` запрещает менять значение программного счётчика на время обращения в память. Этот сигнал должен: -- стать равным единице в тот же такт, когда пришел сигнал `core_req_i` +- стать равным единице в тот же такт, когда пришёл сигнал `core_req_i` - удерживать это значение до тех пор, пока не придет сигнал `mem_ready_i`, но не менее 1 такта (т.е. даже если сигнал `mem_ready_i` будет равен единице, `core_req_i` должен подняться хотя бы на 1 такт). Для реализации подобного функционала вам потребуется вспомогательный регистр `stall_reg`, каждый такт записывающий значение выхода `core_stall_o` и таблица истинности для этого выхода, представленная на _рис. 5_. @@ -195,8 +195,8 @@ _Рисунок 5. Таблица истинности выхода `core_stall_ Реализовать блок загрузки и сохранения со следующим прототипом: -```SystemVerilog -module riscv_lsu( +```Verilog +module lsu( input logic clk_i, input logic rst_i, @@ -223,21 +223,17 @@ module riscv_lsu( ![../../.pic/Labs/lab_08_lsu/fig_05.drawio.svg](../../.pic/Labs/lab_08_lsu/fig_06.drawio.svg) -_Рисунок 6. Структурная схема модуля `riscv_lsu`._ +_Рисунок 6. Функциональная схема модуля `lsu`._ --- ### Порядок выполнения задания 1. Внимательно ознакомьтесь с описанием функционального поведения выходов **LSU**. В случае возникновения вопросов, проконсультируйтесь с преподавателем. -2. Реализуйте модуль `riscv_lsu`. Для этого: - 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `riscv_lsu.sv`. - 2. Опишите в нем модуль `riscv_lsu` с таким же именем и портами, как указано в [задании](#задание). - 1. При описании обратите внимание на то, что большая часть модуля является чисто комбинационной. В этом плане реализация модуля будет частично похожа на реализацию декодера. - 1. При описании мультиплексоров, управляемых сигналом core_size_i посредством конструкции `case`, не забывайте описать блок `default`, иначе вы получите защелку! - 2. Однако помимо комбинационной части, в модуле будет присутствовать и один регистр. -3. После описания модуля его необходимо проверить с помощью тестового окружения. - 1. Тестовое окружение находится [здесь](tb_lsu.sv). - 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_lsu`). - 4. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** +2. Опишите модуль загрузки и сохранения с таким же именем и портами, как указано в задании + 1. При описании обратите внимание на то, что большая часть модуля является чисто комбинационной. В этом плане реализация модуля будет частично похожа на реализацию декодера. + 1. При описании мультиплексоров, управляемых сигналом core_size_i посредством конструкции `case`, не забывайте описать блок `default`, иначе вы получите защелку! + 2. Однако помимо комбинационной части, в модуле будет присутствовать и один регистр. +3. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_08.tb_lsu.sv`](lab_08.tb_lsu.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +4. Данная лабораторная работа не предполагает проверки в ПЛИС. diff --git a/Labs/08. Load-store unit/tb_lsu.sv b/Labs/08. Load-store unit/lab_08.tb_lsu.sv similarity index 97% rename from Labs/08. Load-store unit/tb_lsu.sv rename to Labs/08. Load-store unit/lab_08.tb_lsu.sv index 01c32a76..27206c48 100644 --- a/Labs/08. Load-store unit/tb_lsu.sv +++ b/Labs/08. Load-store unit/lab_08.tb_lsu.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_lsu(); +module lab_08_tb_lsu(); import decoder_pkg::*; logic clk_i ; logic rst_i ; @@ -33,9 +33,9 @@ logic grm_we_o ; logic [ 3:0] grm_be_o ; logic [31:0] grm_addr_o ; logic [31:0] grm_wd_o ; -riscv_lsu dut(.*); +lsu dut(.*); -riscv_lsu_ref grm( +lsu_ref grm( .core_rd_o (grm_rd_o ), .core_stall_o (grm_stall_o), .mem_we_o (grm_we_o ), @@ -53,8 +53,7 @@ always #5 clk_i <= ~clk_i; int err_count; bit not_stopped; initial begin - $display("\n\n===========================\n\nPress button 'Run All' (F3)\n\n===========================\n\n"); - $stop(); + $display("Test has been started."); err_count = 0; not_stopped = 1; clk_i <= 0; @@ -204,7 +203,7 @@ mem_wdata: assert property ( endmodule -module riscv_lsu_ref( +module lsu_ref( input logic clk_i, input logic rst_i, diff --git a/Labs/09. LSU Integration/README.md b/Labs/09. LSU Integration/README.md index 1ba460f2..9dba4062 100644 --- a/Labs/09. LSU Integration/README.md +++ b/Labs/09. LSU Integration/README.md @@ -1,18 +1,23 @@ -# Лабораторная работа 9 "Интеграция блока загрузки и сохранения" +# Лабораторная работа №9 "Интеграция блока загрузки и сохранения" -После реализации блока загрузки и сохранения, его необходимо интегрировать в процессорную систему. На _рис. 1_ представлена схема, иллюстрирующая интеграцию компонентов: +После реализации блока загрузки и сохранения, его необходимо интегрировать в процессорную систему, реализованную в рамках ЛР№7. На _рис. 1_ представлена схема, иллюстрирующая интеграцию компонентов: ![../../.pic/Labs/lab_08_lsu/fig_01.drawio.svg](../../.pic/Labs/lab_08_lsu/fig_01.drawio.svg) _Рисунок 1. Подключение LSU в процессорную систему._ +## Материалы для подготовки к лабораторной работе + +Перед выполнение данной лабораторной работы, рекомендуется изучить теоретическую часть ЛР№8. + ## Задание -Интегрировать модуль `riscv_lsu` в модуль `riscv_unit`. +Интегрировать модуль `lsu` в модуль `processor_system`. ## Порядок выполнения работы -1. Интегрируйте модули `riscv_lsu` и `ext_mem` в модуль `riscv_unit`. - 1. Обратите внимание, что из модуля `riscv_unit` необходимо убрать логику сигнала `stall`, т.к. она была перемещена внутрь модуля `riscv_lsu`. -2. После интеграции модулей, проверьте процессорную систему с помощью [программы](../07.%20Datapath/#Задание) из ЛР№7. +1. Интегрируйте модули `lsu` и `data_mem` в модуль `processor_system`. + 1. Обратите внимание, что из модуля `processor_system` необходимо убрать логику сигнала `stall`, т.к. она была перемещена внутрь модуля `lsu`. +2. После интеграции модулей, проверьте процессорную систему с помощью [программы](../07.%20Datapath/#Задание) и верификационного окружения из ЛР№7. 1. Обратите внимание на то, как теперь исполняются инструкции `sw`, `sh`, `sb`, `lw`, `lh`, `lb`, `lhu`, `lbu`. +3. Данная лабораторная работа не предполагает проверки в ПЛИС. diff --git a/Labs/10. Interrupt subsystem/README.md b/Labs/10. Interrupt subsystem/README.md index 3ac599c5..6d7e54ad 100644 --- a/Labs/10. Interrupt subsystem/README.md +++ b/Labs/10. Interrupt subsystem/README.md @@ -1,15 +1,15 @@ -# Лабораторная работа 10 "Подсистема прерывания" +# Лабораторная работа №10 "Подсистема прерывания" Данная лабораторная работа посвящена изучению систем прерывания в компьютерах и их использованию для обработки программных и аппаратных событий. В процессе работы вы познакомитесь с основными понятиями и принципами работы систем прерывания, а также со средствами программной обработки прерываний. -## Материал для подготовки к лабораторной работе +## Материалы для подготовки к лабораторной работе -- Изучить [теорию по регистрам контроля и статуса](../../Other/CSR.md). +- [Теоретический материал по регистрам контроля и статуса](../../Other/CSR.md). ## Цель -1. Разработать модуль контроллера прерываний. -2. Разработать модуль контроллера регистров статуса и контроля (**CSR**-контроллер). +1. Описать модуль контроллера прерываний. +2. Описать модуль контроллера регистров статуса и контроля (**CSR**-контроллер). ## Ход выполнения @@ -21,7 +21,7 @@ ### Прерывания/Исключения -С компьютером постоянно происходят события, на которые он должен реагировать, запуская соответствующие подпрограммы. Например, при движении мышки нужно перерисовать ее курсор на новом месте или нужно среагировать на подключение флешки и т.п. Возможность запускать нужные подпрограммы в ответ на различные события, возникающие внутри или снаружи компьютера, существенно расширяют его возможности. События, требующие внимания процессора называются **прерываниями** (**interrupt**). Происходящие события формируют запрос на прерывание процессору. +С компьютером постоянно происходят события, на которые он должен реагировать, запуская соответствующие подпрограммы. Например, при движении мышки нужно перерисовать её курсор на новом месте или нужно среагировать на подключение флешки и т.п. Возможность запускать нужные подпрограммы в ответ на различные события, возникающие внутри или снаружи компьютера, существенно расширяют его возможности. События, требующие внимания процессора называются **прерываниями** (**interrupt**). Происходящие события формируют запрос на прерывание процессору. С.А. Орлов, Б.Я. Цилькер в учебнике "Организация ЭВМ и систем" дают следующее определение системе прерывания: @@ -29,19 +29,19 @@ Прерывания делятся на **маски́руемые** — которые при желании можно игнорировать (на которые можно наложить [**битовую маску**](https://ru.wikipedia.org/wiki/Битовая_маска), отсюда ударение на второй слог), и **немаски́руемые** — которые игнорировать нельзя (например сбой генератора тактового синхроимпульса в микроконтроллерах семейства PIC24FJ512GU410[[2, стр. 130]](https://ww1.microchip.com/downloads/aemDocuments/documents/MCU16/ProductDocuments/DataSheets/PIC24FJ512GU410-Family-Data-Sheet-DS30010203D.pdf)). Прерывание похоже на незапланированный вызов функции, вследствие события в аппаратном обеспечении. Программа (функция), запускаемая в ответ на прерывание, называется **обработчиком прерывания**. -События могут быть не только аппаратными, но и программными – синхронными. Такие события называются **исключениями** (**exception**). Программа может столкнуться с состоянием ошибки, вызванным программным обеспечением, таким как неопределенная инструкция, неподдерживаемая данным процессором, в таком случаях говорят, что возникло исключение. К исключениям также относятся сброс, деление на ноль, переполнение и попытки считывания из несуществующей памяти. +События могут быть не только аппаратными, но и программными – синхронными. Такие события называются **исключениями** (**exception**). Программа может столкнуться с состоянием ошибки, вызванным программным обеспечением, таким как неопределённая инструкция, неподдерживаемая данным процессором, в таком случаях говорят, что возникло исключение. К исключениям также относятся сброс, деление на ноль, переполнение и попытки считывания из несуществующей памяти. Важно понимать, что ни прерывание, ни исключение не являются обязательно чем-то плохим. И то и другое — это всего лишь события. Например, с помощью исключений может осуществляться системные вызовы и передача управления отладчику программы. Как и любой другой вызов функции, при возникновении прерывания или исключения необходимо сохранить адрес возврата, перейти к программе обработчика, выполнить свою работу, восстановить контекст (не оставить никаких следов работы обработчика прерывания) и вернуться к программе, которую прервали. -Благодаря исключениям можно реализовать имитацию наличия каких-то аппаратных блоков программными средствами. Например, при отсутствии аппаратного умножителя, можно написать программу обработчика исключения неподдерживаемой инструкции умножения, реализующую алгоритм умножения через сложение и сдвиг. Тогда, каждый раз, когда в программе будет попадаться инструкция умножения, будет возникать исключение, приводящее к запуску обработчика, перемножающего числа и размещающего результат в нужные ячейки памяти. После выполнения обработчика управление возвращается программе, которая даже не поймет, что что-то произошло и умножитель «ненастоящий». +Благодаря исключениям можно реализовать имитацию наличия каких-то аппаратных блоков программными средствами. Например, при отсутствии аппаратного умножителя, можно написать программу обработчика исключения неподдерживаемой инструкции умножения, реализующую алгоритм умножения через сложение и сдвиг. Тогда, каждый раз, когда в программе будет попадаться инструкция умножения, будет возникать исключение, приводящее к запуску обработчика, перемножающего числа и размещающего результат в нужные ячейки памяти. После выполнения обработчика управление возвращается программе, которая даже не поймёт, что что-то произошло и умножитель «ненастоящий». --- На протяжении многих лет, концепция понятия "прерывание" постоянно расширялась. Семейство процессоров 80x86 внесло ещё большую путаницу введя инструкцию `int` (программное прерывание). Многие производители используют такие термины как: **исключение** (_exception_), **ошибка** (_fault_), **отказ** (_abort_), **ловушка** (_trap_) и **прерывание** (_interrupt_), чтобы описать явление, которому посвящена данная лабораторная работа. К несчастью, не существует какого-то чёткого соглашения насчёт этих названий. Разные авторы по-разному приспосабливают эти термины для своего повествования[[3, стр. 995](https://flint.cs.yale.edu/cs422/doc/art-of-asm/pdf/CH17.PDF)]. -Для того, чтобы постараться избежать путаницы, в данной лабораторной работе мы будем использовать три термина, которые введены в спецификации архитектуры RISC-V[[4, стр. 10](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf)], однако имейте в виду, что за пределами данного практикума и спецификации RISC-V в эти термины могут вкладывать другие смыслы. +Для того, чтобы постараться избежать путаницы, в данной лабораторной работе мы будем использовать три термина, которые введены в спецификации архитектуры RISC-V[[4, стр. 18](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf)], однако имейте в виду, что за пределами данного практикума и спецификации RISC-V в эти термины могут вкладывать другие смыслы. Сперва озвучим выдержку из спецификации, а потом дадим этим терминам обывательские определения. @@ -68,9 +68,9 @@ ![../../.pic/Labs/lab_10_irq/fig_01.drawio.svg](../../.pic/Labs/lab_10_irq/fig_01.drawio.svg) -_Рисунок 1. Распределение привилегий по уровням абстракций программного обеспечения [[5, стр.448](https://doi.org/10.1007/978-981-16-9113-3_33)]._ +_Рисунок 1. Распределение привилегий по уровням абстракций программного обеспечения [[5](https://doi.org/10.1007/978-981-16-9113-3_33), стр.448], [[6](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.pdf), стр. 8]._ -Переключение между этими режимами происходит с помощью исключения, называемого **системный вызов**, и который происходит при выполнении специальной инструкции. Для RISC-V такой инструкцией является **ecall**. Это похоже на вызов подпрограммы, но при системном вызове изменяется режим работы и управление передается операционной системе, которая, по коду в инструкции вызова определяет, что от нее хотят. Например, операционная система может предоставить данные с диска, так как запускаемая программа не имеет никакого представления о том, на какой машине ее запустили, или что используется какая-то конкретная файловая система. +Переключение между этими режимами происходит с помощью исключения, называемого **системный вызов**, и который происходит при выполнении специальной инструкции. Для RISC-V такой инструкцией является **ecall**. Это похоже на вызов подпрограммы, но при системном вызове изменяется режим работы и управление передаётся операционной системе, которая, по коду в инструкции вызова определяет, что от неё хотят. Например, операционная система может предоставить данные с диска, так как запускаемая программа не имеет никакого представления о том, на какой машине её запустили, или что используется какая-то конкретная файловая система. Системы прерываний имеет ряд характеристик, которые варьируются в зависимости от их реализации. Все системы можно условно разбить на две категории: обзорные (прямые) и векторные. @@ -95,34 +95,34 @@ _Рисунок 1. Распределение привилегий по уров |0x341 | MRW | mepc | Регистр, хранящий адрес перехваченной инструкции. | |0x342 | MRW | mcause | Причина перехвата | -_Таблица 1. Список регистров, подлежащих реализации в рамках лабораторной работы._ +_Таблица 1. Список регистров, подлежащих реализации в рамках лабораторной работы [[6](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf), стр. 17]._ -По адресу `0x304` должен располагаться регистр, позволяющий маскировать перехваты. Например, если на 5-ом входе системы прерывания генерируется прерывание, то процессор отреагирует на него только в том случае, если 5-ый бит регистра `mie` будет равен 1. Младшие 16 бит этого регистра спецификация RISC-V отводит под маскирование специальных системных прерываний, который не будут поддерживаться нашим процессором (подробней об этом будет в описании регистра mcause). Поэтому в нашей процессорной системе мы будем использовать только старшие 16 бит регистра `mie`, которые отведены для нужд конкретной платформы. +По адресу `0x304` должен располагаться регистр, позволяющий маскировать перехваты. Например, если на 5-ом входе системы прерывания генерируется прерывание, то процессор отреагирует на него только в том случае, если 5-ый бит регистра `mie` будет равен 1. Младшие 16 бит этого регистра спецификация RISC-V отводит под маскирование специальных системных прерываний [[6](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf), стр. 36], которые не будут поддерживаться нашим процессором (подробней об этом будет в описании регистра mcause). Поэтому в нашей процессорной системе мы будем использовать только старшие 16 бит регистра `mie`, которые отведены для нужд конкретной платформы. -По адресу `0x305` должен располагаться регистр `mtvec`, который состоит из двух полей: BASE[31:2] и MODE. Поле BASE хранит старшие 30 бит базового адреса обработчика перехвата (поскольку этот адрес должен быть всегда равен четырем, младшие два бита считаются равными нулю). Поле MODE кодирует тип системы прерывания: +По адресу `0x305` должен располагаться регистр `mtvec`, который состоит из двух полей: BASE[31:2] и MODE. Поле BASE хранит старшие 30 бит базового адреса обработчика перехвата (поскольку этот адрес должен быть всегда равен четырём, младшие два бита считаются равными нулю). Поле MODE кодирует тип системы прерывания: - `MODE == 2'd0` — система прерывания обзорная; - `MODE == 2'd1` — система прерывания векторная. ![../../.pic/Labs/lab_10_irq/fig_02.png](../../.pic/Labs/lab_10_irq/fig_02.png) -_Рисунок 2. Разделение регистра `mtvec` на поля `BASE` и `MODE`_ +_Рисунок 2. Разделение регистра `mtvec` на поля `BASE` и `MODE`[[6](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf), стр. 34]_ -В случае обзорной системы прерывания, любой перехват приводит к загрузке в PC значения базового адреса обработчика перехвата (`PC=BASE`). В векторной системе прерывания исключения обрабатываются таким же способом, как и в обзорной системе, а вот прерывания обрабатываются путем загрузки в PC суммы базового адреса и учетверенного значения причины прерывания (`PC=BASE+4*CAUSE`). +В случае обзорной системы прерывания, любой перехват приводит к загрузке в PC значения базового адреса обработчика перехвата (`PC=BASE`). В векторной системе прерывания исключения обрабатываются таким же способом, как и в обзорной системе, а вот прерывания обрабатываются путём загрузки в PC суммы базового адреса и учетверённого значения причины прерывания (`PC=BASE+4*CAUSE`). -В рамках данной лабораторной работы мы будем реализовывать обзорную систему прерываний. Кроме того, поскольку у обзорной системы прерываний `MODE==0`, что совпадет с тем, что два младших бита базового адреса обработчика перехвата должны быть равны нулю, при перехвате мы можем присваивать программному счетчику значение `mtvec` без каких-либо преобразований. +В рамках данной лабораторной работы мы будем реализовывать обзорную систему прерываний. Кроме того, поскольку у обзорной системы прерываний `MODE==0`, что совпадёт с тем, что два младших бита базового адреса обработчика перехвата должны быть равны нулю, при перехвате мы можем присваивать программному счётчику значение `mtvec` без каких-либо преобразований. Так как обработчик перехвата будет использовать те же регистры, что и прерванная программа, перед использованием регистрового файла, данные из него необходимо сохранить, разместив их на специальном стеке — стеке прерываний. Адрес начала этого стека хранится в регистре `mscratch`, расположенного по адресу `0x340` и по сути является указателем на верхушку стека прерываний. -Регистр `mepc`, расположенный по адресу `0x341` сохраняет адрес инструкции во время исполнения которой произошел перехват. Это очень важно понимать, при реализации **обработчика исключения** — если в нем не перезаписать этот регистр, по возврату из обработчика **процессор снова окажется на инструкции, которая вызвала исключение**. +Регистр `mepc`, расположенный по адресу `0x341` сохраняет адрес инструкции, во время исполнения которой произошёл перехват [[6](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.pdf), стр. 42]. Это очень важно понимать, при реализации **обработчика исключения** — если в нем не перезаписать этот регистр, по возврату из обработчика **процессор снова окажется на инструкции, которая вызвала исключение**. -То как кодируется причина перехвата в регистре `mcause`, расположенного по адресу `0x342` описано в спецификации привилегированной архитектуры[[6, стр. 38]](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf): +То как кодируется причина перехвата в регистре `mcause`, расположенного по адресу `0x342` описано в спецификации привилегированной архитектуры[[6](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.pdf), стр. 43]: -![../../.pic/Labs/lab_10_irq/tab_05.png](../../.pic/Labs/lab_10_irq/tab_05.png) +![../../.pic/Labs/lab_10_irq/tab_02.png](../../.pic/Labs/lab_10_irq/tab_02.png) _Таблица 2. Кодирование причины перехвата в регистре `mcause`._ -Нас интересуют части, выделенные красным. В первую очередь то, как кодируется старший бит регистра `mcause`. Он зависит от типа причины перехвата (`1` в случае прерывания, `0` в случае исключения). Оставшиеся 31 бит регистра отводятся под коды различных причин. Поскольку мы создаем учебный процессор, который не будет использован в реальной жизни, он не будет поддерживать большую часть прерываний/исключений (таких как невыровненный доступ к памяти, таймеры и т.п.). В рамках данного курса мы должны поддерживать исключение по нелегальной инструкции (код 0x02) и должны уметь поддерживать прерывания периферийных устройств (под которые зарезервированы коды начиная с 16-го, именно поэтому мы будем использовать только старшие 16 бит регистра `mie`). В рамках данной лабораторной работы процессор будет поддерживать только один источник прерывания, поэтому для кодирования причины прерывания нам потребуется только первый код из диапазона _"Designated for platform use"_. В случае, если вы захотите расширить количество источников прерываний, вы можете выполнить вспомогательную [лабораторную работу №12](../12.%20Daisy%20chain). +Нас интересуют части , выделенные цветом. В первую очередь то, как кодируется старший бит регистра `mcause` (выделено синим). Он зависит от типа причины перехвата (`1` в случае прерывания, `0` в случае исключения). Оставшиеся 31 бит регистра отводятся под коды различных причин. Поскольку мы создаём учебный процессор, который не будет использован в реальной жизни, он не будет поддерживать большую часть прерываний/исключений (таких как невыровненный доступ к памяти, таймеры и т.п.). В рамках данного курса мы должны поддерживать исключение по нелегальной инструкции (код 0x02, выделено красным) и должны уметь поддерживать прерывания периферийных устройств (под которые зарезервированы коды начиная с 16-го, именно поэтому мы будем использовать только старшие 16 бит регистра `mie`). В рамках данной лабораторной работы процессор будет поддерживать только один источник прерывания, поэтому для кодирования причины прерывания нам потребуется только первый код из диапазона _"Designated for platform use"_ (выделено зелёным). В случае, если вы захотите расширить количество источников прерываний, вы можете выполнить вспомогательную [лабораторную работу №12](../12.%20Daisy%20chain). Таким образом: в случае если произошло исключение (в связи с нелегальной инструкцией), значение `mcause` должно быть `0x00000002`. Если произошло прерывание, значение `mcause` должно быть `0x80000010`. @@ -152,9 +152,9 @@ _Таблица 2. Кодирование причины перехвата в Контроллер прерываний – это блок процессора, обеспечивающий взаимодействие с устройствами, запрашивающими прерывания, формирование кода причины прерывания для процессора, маскирование прерываний. В некоторых реализация, контроллер прерываний может реагировать на прерывания в соответствии с приоритетом. -Периферийное устройство, которое может генерировать прерывание, подключается к контроллеру прерывания парой проводов: "запрос на прерывание" (`irq_req_i`) и "прерывание обслужено" (`irq_ret_o`). Предположим, к контроллеру прерываний подключили клавиатуру. Когда на ней нажимают клавишу, код этой клавиши попадает в буферный регистр с дополнительным управляющим битом, выставленным в единицу, который подключен к входу запроса на прерывание. Если прерывание не замаскировано (в нашем процессоре это означает, что нулевой бит регистра `mie` выставлен в 1), то контроллер прерывания сгенерирует код причины прерывания (в нашем случае — это константа `0x80000010`). Кроме этого, контроллер прерывания подаст сигнал `irq_o`, чтобы устройство управления процессора узнало, что произошло прерывание и разрешило обновить содержимое регистра причины `mcause`, сохранило адрес прерванной инструкции в `mepc` и загрузило в `PC` вектор прерывания `mtvec`. +Периферийное устройство, которое может генерировать прерывание, подключается к контроллеру прерывания парой проводов: "запрос на прерывание" (`irq_req_i`) и "прерывание обслужено" (`irq_ret_o`). Предположим, к контроллеру прерываний подключили клавиатуру. Когда на ней нажимают клавишу, код этой клавиши попадает в буферный регистр с дополнительным управляющим битом, выставленным в единицу, который подключён к входу запроса на прерывание. Если прерывание не замаскировано (в нашем процессоре это означает, что нулевой бит регистра `mie` выставлен в 1), то контроллер прерывания сгенерирует код причины прерывания (в нашем случае — это константа `0x80000010`). Кроме этого, контроллер прерывания подаст сигнал `irq_o`, чтобы устройство управления процессора узнало, что произошло прерывание и разрешило обновить содержимое регистра причины `mcause`, сохранило адрес прерванной инструкции в `mepc` и загрузило в `PC` вектор прерывания `mtvec`. -Когда будет выполняться инструкция `mret`, устройство управления подаст сигнал контроллеру прерывания, чтобы тот, в свою очередь, направил его в виде сигнала «прерывание обслужено» для соответствующего устройства. После этого периферийное устройство обязано снять сигнал запроса прерывания хотя бы на один такт. В нашем примере сигнал «прерывание обслужено» может быть подключен непосредственно к сбросу буферного регистра клавиатуры. +Когда будет выполняться инструкция `mret`, устройство управления подаст сигнал контроллеру прерывания, чтобы тот, в свою очередь, направил его в виде сигнала «прерывание обслужено» для соответствующего устройства. После этого периферийное устройство обязано снять сигнал запроса прерывания хотя бы на один такт. В нашем примере сигнал «прерывание обслужено» может быть подключён непосредственно к сбросу буферного регистра клавиатуры. ## Структура разрабатываемых устройств @@ -168,11 +168,11 @@ _Таблица 2. Кодирование причины перехвата в _Рисунок 3. Место разрабатываемых блоков в структуре процессора._ -Пока что вам нужно реализовать только блоки **irq controller** и **control status registers**, а не саму схему, приведенную выше. +Пока что вам нужно реализовать только блоки **irq controller** и **control status registers**, а не саму схему, приведённую выше. ### CSR-контроллер -Рассмотрим один из возможных вариантов организации блока **Control and Status Registers**. Основная работа по описанию схемы блока состоит в описании мультиплексора и демультиплексора. Мультиплексор подает на выход **read_data_o** значение регистра, который соответствует пришедшему адресу. В свою же очередь, демультиплексор маршрутизирует сигнал разрешения на запись **write_enable_i** (en) на тот же регистр. +Рассмотрим один из возможных вариантов организации блока **Control and Status Registers**. Основная работа по описанию схемы блока состоит в описании мультиплексора и демультиплексора. Мультиплексор подаёт на выход **read_data_o** значение регистра, который соответствует пришедшему адресу. В свою же очередь, демультиплексор маршрутизирует сигнал разрешения на запись **write_enable_i** (en) на тот же регистр. ![../../.pic/Labs/lab_10_irq/fig_04.drawio.svg](../../.pic/Labs/lab_10_irq/fig_04.drawio.svg) @@ -182,7 +182,7 @@ _Рисунок 4. Структурная схема контроллера CS- Для реализации мультиплексора на языке описания аппаратуры SystemVerilog можно воспользоваться конструкцией `case` внутри блока **always_comb**. Для реализации демультиплексора также можно использовать `case`, только если при описании мультиплексора в зависимости от управляющего сигнала на один и тот же выход идут разные входы, то при описании демультиплексора все будет наоборот: в зависимости от управляющего сигнала, один и тот же вход будет идти на разные выходы (например, на разные биты многоразрядной шины `enable`). -Мультиплексоры, располагаемые на входах регистров `mepc` и `mcause` нужны, чтобы при возникновении сигнала прерывания сразу же разрешить обновить значение этих регистров значением `pc_i`, на котором произошел перехват и кодом причины происходящего сейчас перехвата. +Мультиплексоры, располагаемые на входах регистров `mepc` и `mcause` нужны, чтобы при возникновении сигнала прерывания сразу же разрешить обновить значение этих регистров значением `pc_i`, на котором произошёл перехват и кодом причины происходящего сейчас перехвата. ### Контроллер прерываний @@ -201,17 +201,17 @@ _Рисунок 5. Структурная схема контроллера пр Регистры отслеживания обработки прерывания и исключения нужны для того, чтобы мы могли понимать, что в данный момент процессор уже выполняет обработку прерывания / исключения. В такие моменты (если любой из регистров `exc_h`/`irq_h` содержит значение `1`) все последующие запросы на прерывание игнорируются. За это отвечают вентили И и ИЛИ-НЕ в правом верхнем углу схемы. -Однако возможна ситуация возникновения исключения во время обработки прерывания — в этом случае, оба регистра будут хранить значение `1`. В момент возврата из обработчика, придет сигнал `mret_i`, который в первую очередь сбросит регистр `exc_h` и только если тот равен нулю, сбросит регистр `irq_h`. +Однако возможна ситуация возникновения исключения во время обработки прерывания — в этом случае, оба регистра будут хранить значение `1`. В момент возврата из обработчика, придёт сигнал `mret_i`, который в первую очередь сбросит регистр `exc_h` и только если тот равен нулю, сбросит регистр `irq_h`. -Исключение во время обработки исключения не поддерживается данной микроархитектурой и скорее всего приведет к циклическому вызову обработчика исключения. Поэтому код обработчика исключений должен быть написан с особым вниманием. +Исключение во время обработки исключения не поддерживается данной микроархитектурой и скорее всего приведёт к циклическому вызову обработчика исключения. Поэтому код обработчика исключений должен быть написан с особым вниманием. Логика установки и сброса регистров `irq_h` и `exc_h` работает следующим образом: - если сигнал, обозначенный в прямоугольнике как `reset` равен единице, в регистр будет записано значение `0`; - если сигнал, обозначенный в прямоугольнике как `set` равен единице, в регистр будет записано значение `1`; -- в остальных случах, регистр сохраняет свое значение. +- в остальных случаях, регистр сохраняет своё значение. -Обратите внимание, что логика установки и сброса регистров дает приоритет сбросу, хотя сигнал сброса никогда не придет одновременно с сигналом установки (поскольку инструкция `mret` не генерирует исключение, сигнал `mret_i` никогда не придет одновременно с сигналом `exception_i`, а логика приоритета исключений над прерываниями не даст сигналу `mret` распространиться до регистра `irq_h` одновременно с формированием сигнала `irq_o`). +Обратите внимание, что логика установки и сброса регистров даёт приоритет сбросу, хотя сигнал сброса никогда не придёт одновременно с сигналом установки (поскольку инструкция `mret` не генерирует исключение, сигнал `mret_i` никогда не придёт одновременно с сигналом `exception_i`, а логика приоритета исключений над прерываниями не даст сигналу `mret` распространиться до регистра `irq_h` одновременно с формированием сигнала `irq_o`). Логика приоритета исключений над прерываниями заключается в том, что сигнал `exception_i` является частью логики обработки вложенных прерываний. Пройдя через два логических ИЛИ и последующий инвертор, этот сигнал обнулит запрос на прерывание на логическом И в правом верхнем углу. @@ -219,7 +219,7 @@ _Рисунок 5. Структурная схема контроллера пр ## Пример обработки перехвата -Ниже представлен пример программы и обработчика перехватов. Программа начинается с инициализации начальных значений регистров управления, указателя на верхушку стека и глобальную область данных, после чего уходит в бесконечный цикл ничего не делая, до тех пор, пока не произойдет перехват. +В _листинге 1_ представлен пример программы с обработчиком перехватов. Программа начинается с инициализации начальных значений регистров управления, указателя на верхушку стека и глобальную область данных, после чего уходит в бесконечный цикл ничего не делая, до тех пор, пока не произойдёт перехват. Алгоритм работы обработчика перехвата (`trap handler`) выглядит следующим образом: @@ -338,7 +338,7 @@ done: 1. Описать на языке SystemVerilog модуль контроллера регистров статуса и контроля (**CSR**-контроллер) со следующим прототипом: -```SystemVerilog +```Verilog module csr_controller( input logic clk_i, @@ -367,7 +367,7 @@ endmodule 2. Описать на языке SystemVerilog модуль контроллера прерываний со следующим прототипом: -```SystemVerilog +```Verilog module interrupt_controller( input logic clk_i, input logic rst_i, @@ -387,30 +387,21 @@ endmodule ## Порядок выполнения задания 1. Внимательно ознакомьтесь с описанием модуля `csr_controller` и его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем. -2. Реализуйте модуль `csr_controller`. Для этого: - 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `csr_controller.sv`. - 2. Опишите в нем модуль `csr_controller` с таким же именем и портами, как указано в [задании](#задание). - 3. Обратите внимание на наличие импорта пакета `csr_pkg`, данный пакет содержит адреса используемых регистров контроля и статуса, которыми будет удобно пользоваться при реализации модуля. -3. После описания модуля его необходимо проверить с помощью тестового окружения. - 1. Тестовое окружение находится [здесь](tb_csr.sv). - 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_csr`). - 4. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** -4. Внимательно ознакомьтесь с описанием функционального поведения сигналов `interrupt_controller`, а также его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем. -5. Реализуйте модуль `interrupt_controller`. Для этого: - 1. В `Design Sources` проекта с предыдущих лаб, создайте `SystemVerilog`-файл `interrupt_controller.sv`. - 2. Опишите в нем модуль `interrupt_controller` с таким же именем и портами, как указано в [задании](#задание). -6. После описания модуля его необходимо проверить с помощью тестового окружения. - 1. Тестовое окружение находится [здесь](tb_irq.sv). - 2. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`tb_irq`). - 4. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** +2. Добавьте в `Design Sources` проекта файл [сsr_pkg.sv](csr_pkg.sv). Данный файл содержит пакет с адресами регистров контроля и статуса, а также кодами команд для взаимодействия с ними. +3. Опишите модуль `csr_controller` с таким же именем и портами, как указано в задании. +4. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_10.tb_csr.sv`](lab_10.tb_csr.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +5. Внимательно ознакомьтесь с описанием функционального поведения сигналов `interrupt_controller`, а также его структурной схемой. В случае возникновения вопросов, проконсультируйтесь с преподавателем. +6. Опишите модуль `interrupt_controller` с таким же именем и портами, как указано в задании. +7. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_10.tb_irq.sv`](lab_10.tb_irq.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +8. Данная лабораторная работа не предполагает проверки в ПЛИС. ## Список использованной литературы 1. С.А. Орлов, Б.Я. Цилькер / Организация ЭВМ и систем: Учебник для вузов. 2-е изд. / СПб.: Питер, 2011. 2. [PIC24FJ512GU410 Family Data Sheet](https://ww1.microchip.com/downloads/aemDocuments/documents/MCU16/ProductDocuments/DataSheets/PIC24FJ512GU410-Family-Data-Sheet-DS30010203D.pdf) 3. [The Art of Assembly Language](https://flint.cs.yale.edu/cs422/doc/art-of-asm/pdf/) -4. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf) +4. [The RISC-V Instruction Set Manual Volume I: Unprivileged ISA](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf) 5. [Pillai, V.P., Megalingam, R.K. (2022). System Partitioning with Virtualization for Federated and Distributed Machine Learning on Critical IoT Edge Systems. In: Saraswat, M., Sharma, H., Balachandran, K., Kim, J.H., Bansal, J.C. (eds) Congress on Intelligent Systems. Lecture Notes on Data Engineering and Communications Technologies, vol 111. Springer, Singapore.](https://doi.org/10.1007/978-981-16-9113-3_33) -6. [The RISC-V Instruction Set Manual Volume II: Privileged Architecture](https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf) +6. [The RISC-V Instruction Set Manual Volume II: Privileged Architecture](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/priv-isa-asciidoc.pdf) diff --git a/Labs/10. Interrupt subsystem/tb_csr.sv b/Labs/10. Interrupt subsystem/lab_10.tb_csr.sv similarity index 99% rename from Labs/10. Interrupt subsystem/tb_csr.sv rename to Labs/10. Interrupt subsystem/lab_10.tb_csr.sv index 141c5e6e..a3c71afa 100644 --- a/Labs/10. Interrupt subsystem/tb_csr.sv +++ b/Labs/10. Interrupt subsystem/lab_10.tb_csr.sv @@ -9,7 +9,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_csr(); +module lab_10_tb_csr(); logic clk_i; logic rst_i; logic trap_i; diff --git a/Labs/10. Interrupt subsystem/tb_irq.sv b/Labs/10. Interrupt subsystem/lab_10.tb_irq.sv similarity index 99% rename from Labs/10. Interrupt subsystem/tb_irq.sv rename to Labs/10. Interrupt subsystem/lab_10.tb_irq.sv index 72b5a194..0d0c9dd5 100644 --- a/Labs/10. Interrupt subsystem/tb_irq.sv +++ b/Labs/10. Interrupt subsystem/lab_10.tb_irq.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_irq(); +module lab_10_tb_irq(); logic clk_i; logic rst_i; logic exception_i; diff --git a/Labs/11. Interrupt integration/README.md b/Labs/11. Interrupt integration/README.md index fad2af4f..6747116d 100644 --- a/Labs/11. Interrupt integration/README.md +++ b/Labs/11. Interrupt integration/README.md @@ -1,6 +1,6 @@ -# Лабораторная работа 11 "Интеграция подсистемы прерывания" +# Лабораторная работа №11 "Интеграция подсистемы прерывания" -После реализации подсистемы прерывания, её необходимо интегрировать в процессорную систему. Для этого необходимо обновить модуль `riscv_core` по схеме, приведенной на _рис. 1_: +После реализации подсистемы прерывания, её необходимо интегрировать в процессорную систему. Для этого необходимо обновить модуль `processor_core` по схеме, приведённой на _рис. 1_: ![../../.pic/Labs/lab_11_irq_integration/fig_01.drawio.svg](../../.pic/Labs/lab_11_irq_integration/fig_01.drawio.svg) @@ -17,11 +17,10 @@ _Рисунок 2. Схема без выделения новых частей ## Задание -Интегрировать модули `csr_controller` и `irq_controller` в модуль `riscv_core`. При этом у модуля `riscv_core` будет обновленный прототип (поскольку добавился вход `irq_req_i` и `irq_ret_o`): - -```SystemVerilog -module riscv_core ( +Интегрировать модули `csr_controller` и `irq_controller` в модуль `processor_core`. При этом у модуля `processor_core` будет обновлённый прототип (поскольку добавился вход `irq_req_i` и `irq_ret_o`): +```Verilog +module processor_core ( input logic clk_i, input logic rst_i, @@ -40,16 +39,20 @@ module riscv_core ( ); ``` -Обновите описание создания модуля `riscv_core` в модуле `riscv_unit` с учетом появившихся портов. Для этого создайте провода `irq_req` и `irq_ret` и подключите их к соответствующим входам `riscv_core`. Другим концом эти провода не будут пока что ни к чему подключены — это изменится в [ЛР№13](../13.%20Peripheral%20units/). +Обновите описание создания модуля `processor_core` в модуле `processor_system` с учётом появившихся портов. Для этого создайте провода `irq_req` и `irq_ret` и подключите их к соответствующим входам `processor_core`. Другим концом эти провода не будут пока что ни к чему подключены — это изменится в [ЛР№13](../13.%20Peripheral%20units/). В случае, если вы захотите расширить количество источников прерывания, вы можете выполнить вспомогательную [ЛР№12](../12.%20Daisy%20chain). ## Порядок выполнения работы -1. Интегрируйте модули `csr_controller` и `irq_controller` в модуль `riscv_core`. - 1. Обратите внимание, что что в модуле `riscv_core` появились новые входные и выходные сигналы: `irq_req_i` и `irq_ret_o`. Эти порты должны быть использованы при подключении `riscv_core` в модуле `riscv_unit`. - 1. Ко входу `irq_req_i` должен быть подключен провод `irq_req`, другой конец которого пока не будет ни к чему подключен. +1. Замените файл `program.mem` в `Design Sources` проекта новым файлом [program.mem](program.mem), приложенном в данной лабораторной работе. Данный файл содержит программу из _листинга 1_ ЛР№10. +2. Интегрируйте модули `csr_controller` и `irq_controller` в модуль `processor_core`. + 1. Обратите внимание, что что в модуле `processor_core` появились новые входные и выходные сигналы: `irq_req_i` и `irq_ret_o`. Эти порты должны быть использованы при подключении `processor_core` в модуле `processor_system`. + 1. Ко входу `irq_req_i` должен быть подключён провод `irq_req`, другой конец которого пока не будет ни к чему подключён. 2. К выходу `irq_ret_o` необходимо подключить провод `irq_ret`, который также пока не будет использован. 3. Имена проводов `irq_req` и `irq_ret` должны быть именно такими, т.к. используются верификационным окружением при проверке данной лабораторной работы. 2. Обратите внимание на то, что появилась константа `imm_Z` — это единственная константа ядра, которая расширяется нулями, а не знаковым битом. -2. После интеграции модулей проверьте процессорную систему с помощью [программы](irq_program.mem), текст которой [был представлен](../10.%20Interrupt%20subsystem#пример-обработки-перехвата) в ЛР10 с помощью предоставленного [тестбенча](tb_irq_unit.sv). \ No newline at end of file +3. Проверьте модуль с помощью верификационного окружения, представленного в файле [lab_11.tb_processor_system.sv](lab_11.tb_processor_system.sv). + 1. Перед запуском симуляции убедитесь, что выбран правильный модуль верхнего уровня в `Simulation Sources`. + 2. Как и в случае с проверкой процессора архитектуры CYBERcobra, вам не будет сказано пройден тест или нет. Вам необходимо самостоятельно, такт за тактом проверить что процессор правильно выполняет [описанные](../10.%20Interrupt%20subsystem#пример-обработки-перехвата) в _Листинге 1_ ЛР№10 инструкции (см. порядок выполнения задания ЛР№4). Для этого, необходимо сперва самостоятельно рассчитать что именно должна сделать данная инструкция, а потом проверить что процессор сделал именно это. +4. Данная лабораторная работа не предполагает проверки в ПЛИС. diff --git a/Labs/11. Interrupt integration/tb_irq_unit.sv b/Labs/11. Interrupt integration/lab_11.tb_processor_system.sv similarity index 79% rename from Labs/11. Interrupt integration/tb_irq_unit.sv rename to Labs/11. Interrupt integration/lab_11.tb_processor_system.sv index 60405c6a..7f88c542 100644 --- a/Labs/11. Interrupt integration/tb_irq_unit.sv +++ b/Labs/11. Interrupt integration/lab_11.tb_processor_system.sv @@ -8,12 +8,12 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_irq_unit(); +module lab_11_tb_processor_system(); reg clk; reg rst; - riscv_unit unit( + processor_system system( .clk_i(clk), .rst_i(rst) ); @@ -29,17 +29,17 @@ module tb_irq_unit(); always #10 clk = ~clk; initial begin - $display( "\nStart test: \n\n==========================\nCLICK THE BUTTON 'Run All'\n==========================\n"); $stop(); - unit.irq_req = 0; + $display( "\nTest has been started"); + system.irq_req = 0; rst = 1; #40; rst = 0; repeat(20)@(posedge clk); - unit.irq_req = 1; - while(unit.irq_ret == 0) begin + system.irq_req = 1; + while(system.irq_ret == 0) begin @(posedge clk); end - unit.irq_req = 0; + system.irq_req = 0; repeat(20)@(posedge clk); $display("\n The test is over \n See the internal signals of the module on the waveform \n"); $finish; diff --git a/Labs/11. Interrupt integration/irq_program.mem b/Labs/11. Interrupt integration/program.mem similarity index 100% rename from Labs/11. Interrupt integration/irq_program.mem rename to Labs/11. Interrupt integration/program.mem diff --git a/Labs/12. Daisy chain/README.md b/Labs/12. Daisy chain/README.md index f0d27715..ef3c9ca8 100644 --- a/Labs/12. Daisy chain/README.md +++ b/Labs/12. Daisy chain/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 12 "Блок приоритетных прерываний" +# Лабораторная работа №12 "Блок приоритетных прерываний" В базовом варианте лабораторных работ предлагается реализовать процессорную систему с одним источником прерываний, чего достаточно для выполнения лабораторных работ. Однако, если появится желание усовершенствовать систему и увеличить количество периферийных устройств, то поддержка только одного источника прерываний создаст множество сложностей. В рамках данной лабораторной работы необходимо реализовать блок приоритетных прерываний и интегрировать его в контроллер прерываний, увеличив число потенциальных источников прерываний до 16. @@ -15,7 +15,7 @@ _Рисунок 1. Структурная схема daisy chain._ -Данная схема состоит из двух массивов элементов И. Первый массив (верхний ряд элементов) формирует многоразрядный сигнал (назовем его для определенности `ready`, на _рис. 1_ он обозначен как "_Приоритет_"), который перемножается с запросами с помощью массива элементов И нижнего ряда, формируя многоразрядный сигнал `y`. Обратите внимание на то, что результат операции И на очередном элементе нижнего массива влияет на результат И следующего за ним элемента верхнего массива и наоборот (`readyₙ₊₁` зависит от `yₙ`, в то время как `yₙ` зависит от `readyₙ`). Как только на одном из разрядов `y` появится значение `1`, оно сразу же распространится в виде `0` по всем оставшимся последующим разрядам `ready`, обнуляя их. А приняв нулевое значение, разряды `ready` обнулят соответствующие разряды `y` (нулевые разряды `ready` запрещают генерацию прерывания для соответствующих разрядов `y`). +Данная схема состоит из двух массивов элементов И. Первый массив (верхний ряд элементов) формирует многоразрядный сигнал (назовём его для определённости `ready`, на _рис. 1_ он обозначен как "_Приоритет_"), который перемножается с запросами с помощью массива элементов И нижнего ряда, формируя многоразрядный сигнал `y`. Обратите внимание на то, что результат операции И на очередном элементе нижнего массива влияет на результат И следующего за ним элемента верхнего массива и наоборот (`readyₙ₊₁` зависит от `yₙ`, в то время как `yₙ` зависит от `readyₙ`). Как только на одном из разрядов `y` появится значение `1`, оно сразу же распространится в виде `0` по всем оставшимся последующим разрядам `ready`, обнуляя их. А приняв нулевое значение, разряды `ready` обнулят соответствующие разряды `y` (нулевые разряды `ready` запрещают генерацию прерывания для соответствующих разрядов `y`). Нижний массив элементов И можно описать через непрерывное присваивание побитового И между `ready` и сигналом запросов на прерывание. @@ -25,7 +25,7 @@ _Рисунок 1. Структурная схема daisy chain._ Индексы, используемые конструкцией, должны быть объявлены с помощью ключевого слова `genvar`. Далее, в области, ограниченной ключевыми словами `generate`/`endgenerate` описывается цикл присваиваний (в подобном цикле можно и создавать модули): -```SystemVerilog +```Verilog logic [4:0] a; logic [4:0] b; @@ -39,11 +39,13 @@ generate endgenerate ``` -Разумеется в этом примере можно было бы просто сделать одно непрерывное присваивание `assign a = b;`, однако в случае реализации верхнего ряда элементов И, подобное многобитное непрерывное присваивание не приведет к синтезу требуемой схемы. +_Листинг 1. Пример использования конструкции generate._ + +Разумеется в этом примере можно было бы просто сделать одно непрерывное присваивание `assign a = b;`, однако в случае реализации верхнего ряда элементов И, подобное многобитное непрерывное присваивание не приведёт к синтезу требуемой схемы. ## Практика -Рассмотрим реализацию нашего контроллера прерываний: +Рассмотрим реализацию контроллера прерываний, представленную на _рис. 2_. ![../../.pic/Labs/lab_12_daisy_chain/fig_02.drawio.svg](../../.pic/Labs/lab_12_daisy_chain/fig_02.drawio.svg) @@ -60,9 +62,9 @@ _Рисунок 2. Структурная схема блока приорите Внутренний сигнал `cause` является сигналом `y` с _рис. 1_. Как пояснялось выше, этот сигнал может содержать только одну единицу, она будет соответствовать прошедшему запросу на прерывание. А значит этот результат можно использовать в качестве сигнала для идентификации причины прерывания. При этом, свертка по ИЛИ (операция ИЛИ между всеми битами) этого сигнала даст итоговый запрос на прерывание. -Однако, как упоминалось в [ЛР10](../10.%20Interrupt%20subsystem/), спецификация RISC-V накладывает определенные требования на кодирование кода `mcause` для причины прерывания. В частности, необходимо выставить старший бит в единицу, а значение на оставшихся битах должно быть больше 16. Схемотехнически это проще реализовать выполнив склейку `{12'h800, cause, 4'b0000}` — в этом случае старший разряд будет равен единице, и если хоть один разряд `cause` будет равен единице (а именно это и является критерием появления прерывания), младшие 31 бит `mcause` будут больше 16. +Однако, как упоминалось в [ЛР№10](../10.%20Interrupt%20subsystem/), спецификация RISC-V накладывает определенные требования на кодирование кода `mcause` для причины прерывания. В частности, необходимо выставить старший бит в единицу, а значение на оставшихся битах должно быть больше 16. Схемотехнически это проще реализовать выполнив склейку `{12'h800, cause, 4'b0000}` — в этом случае старший разряд будет равен единице, и если хоть один разряд `cause` будет равен единице (а именно это и является критерием появления прерывания), младшие 31 бит `mcause` будут больше 16. -Регистр на _рис. 2_ хранит значение внутреннего сигнала `cause`, чтобы по завершению прерывания выставить единицу на соответствующем разряде сигнала `irq_ret_o`, который сообщит устройству, чье прерывание обрабатывалось ранее, что его обработка завершена. +Регистр на _рис. 2_ хранит значение внутреннего сигнала `cause`, чтобы по завершению прерывания выставить единицу на соответствующем разряде сигнала `irq_ret_o`, который сообщит устройству, чьё прерывание обрабатывалось ранее, что его обработка завершена. ## Задание @@ -74,14 +76,17 @@ _Рисунок 2. Структурная схема блока приорите _Рисунок 3. Структурная схема блока приоритетных прерываний._ -Обратите внимание, что разрядность сигналов `irq_req_i`, `mie_i`, `irq_ret_o` изменилась. Теперь это 16-разрядные сигналы. Сигнал, который ранее шел на выход к `irq_ret_o` теперь идет на вход `irq_ret_i` модуля `daisy_chain`. Формирование кода причины прерывания `irq_cause_o` перенесено в модуль `daisy_chain`. +Обратите внимание, что разрядность сигналов `irq_req_i`, `mie_i`, `irq_ret_o` изменилась. Теперь это 16-разрядные сигналы. Сигнал, который ранее шёл на выход к `irq_ret_o` теперь идёт на вход `irq_ret_i` модуля `daisy_chain`. Формирование кода причины прерывания `irq_cause_o` перенесено в модуль `daisy_chain`. ## Порядок выполнения работы 1. Опишите модуль `daisy_chain`. 1. При формировании верхнего массива элементов И с _рис. 2_, вам необходимо воспользоваться сформировать 16 непрерывных присваиваний через блок `generate for`. 2. Формирование нижнего массива элементов И можно сделать с помощью одного непрерывного присваивания посредством операции побитовое И. - 3. Проверьте модуль `daisy_chain` с помощью модуля [`tb_daisy_chain`](tb_daisy_chain.sv). -2. Интегрируйте модуль `daisy_chain` в модуль `irq_controller` по схеме, представленной на _рис. 3_. - 1. Не забудьте обновить разрядность сигналов `irq_req_i`, `mie_i`, `irq_ret_o`. - 2. Также не забудьте обновить разрядность сигналов `irq_req_i`, `irq_ret_o` в `riscv_core` и `riscv_unit`, также использовать младшие 16 бит сигнала `mie` вместо одного при подключении модуля `irq_controller`. +2. Проверьте модуль `daisy_chain` с помощью верификационного окружения, представленного в файле [`lab_12.tb_daisy_chain`](lab_12.tb_daisy_chain.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. +3. Интегрируйте модуль `daisy_chain` в модуль `irq_controller` по схеме, представленной на _рис. 3_. + 1. Не забудьте обновить разрядность сигналов `irq_req_i`, `mie_i`, `irq_ret_o` в модуле `irq_controller`. + 2. Также не забудьте обновить разрядность сигналов `irq_req_i`, `irq_ret_o` в модулях `processor_core` и `processor_system`. + 3. Кроме того, теперь вам нужно использовать старшие 16 бит сигнала `mie` вместо одного при подключении модуля `irq_controller` в модуле `processor_core`. +4. Проверьте с помощью верификационного окружения из ЛР№11, что в процессе интеграции ничего не сломалось. diff --git a/Labs/12. Daisy chain/tb_daisy_chain.sv b/Labs/12. Daisy chain/lab_12.tb_daisy_chain.sv similarity index 98% rename from Labs/12. Daisy chain/tb_daisy_chain.sv rename to Labs/12. Daisy chain/lab_12.tb_daisy_chain.sv index 41b09c7e..b019d63f 100644 --- a/Labs/12. Daisy chain/tb_daisy_chain.sv +++ b/Labs/12. Daisy chain/lab_12.tb_daisy_chain.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_daisy_chain(); +module lab_12_tb_daisy_chain(); logic clk_i, rst_i, ready_i, irq_ret_i; logic [15:0] masked_irq_i; diff --git a/Labs/13. Peripheral units/README.md b/Labs/13. Peripheral units/README.md index 34d8c482..7081d128 100644 --- a/Labs/13. Peripheral units/README.md +++ b/Labs/13. Peripheral units/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 13 "Периферийные устройства" +# Лабораторная работа №13 "Периферийные устройства" В [ЛР№11](../11.%20Interrupt%20integration/) вы закончили реализовывать свой собственный RISC-V процессор. Однако пока что он находится "в вакууме" и никак не связан с внешним миром. Для исправления этого недостатка вами будет реализована системная шина, через которую к процессору смогут подключаться различные периферийные устройства. @@ -6,28 +6,21 @@ Интегрировать периферийные устройства в процессорную систему. ---- - -## Материал для подготовки к лабораторной работе - -Для успешного выполнения лабораторной работы, вам необходимо: +## Материалы для подготовки к лабораторной работе -- ознакомиться с [примером описания модуля-контроллера](../../Basic%20Verilog%20structures/Controllers.md); -- ознакомиться с [описанием](#описание-контроллеров-периферийных-устройств) контроллеров периферийных устройств. +- [Пример описания модуля-контроллера](../../Basic%20Verilog%20structures/Controllers.md); ## Ход работы -1. Изучить теорию об адресном пространстве -2. Получить индивидуальный вариант со своим набором периферийных устройств -3. Интегрировать контроллеры периферийных устройств в адресное пространство вашей системы -4. Собрать финальную схему вашей системы -5. Проверить работу системы в ПЛИС с помощью демонстрационного ПО, загружаемого в память инструкций - ---- +1. Изучить теорию об адресном пространстве. +2. Получить индивидуальный вариант со своим набором периферийных устройств. +3. Интегрировать контроллеры периферийных устройств в адресное пространство вашей системы. +4. Собрать финальную схему вашей системы. +5. Проверить работу системы в ПЛИС с помощью демонстрационного ПО, загружаемого в память инструкций. ## Теория -Помимо процессора и памяти, третьим ключевым элементом вычислительной системы является система ввода/вывода, обеспечивающая обмен информации между ядром вычислительной машины и периферийными устройствами[1, стр. 364]. +Помимо процессора и памяти, третьим ключевым элементом вычислительной системы является система ввода/вывода, обеспечивающая обмен информации между ядром вычислительной машины и периферийными устройствами [1, стр. 364]. Любое периферийное устройство со стороны вычислительной машины видится как набор ячеек памяти (регистров). С помощью чтения и записи этих регистров происходит обмен информации с периферийным устройством, и управление им. Например, датчик температуры может быть реализован самыми разными способами, но для процессора он в любом случае ячейка памяти, из которой он считывает число – температуру. @@ -42,7 +35,7 @@ 2. Светодиоды 3. Клавиатура PS/2 4. Семисегментные индикаторы -5. UART-приемник +5. UART-приёмник 6. UART-передатчик 7. Видеоадаптер @@ -54,6 +47,8 @@ _Рисунок 1. Итоговая структура процессорной системы._ +Обратите внимание на то, что на вход `mem_ready_i` модуля `lsu` подаётся единица. Вообще говоря, каждый модуль-контроллер периферийного устройства должен содержать выходной сигнал `ready_o`, который должен мультиплексироваться с остальными подобно тому, как мультиплексируются сигналы read_data_o. На вход `lsu` должен подаваться выход мультиплексора. Однако, поскольку все модули достаточно просты, чтобы, как и у памяти данных, выходной сигнал `ready_o` был всегда равен единице (а также для упрощения _рис. 1_), эти сигналы были убраны из микроархитектуры. В случае, если вы решите добавить в процессорную систему периферийное устройство, сигнал `ready_o` которого не будет равен константной единице, логику управления входом `mem_ready_i` модуля `lsu` будет необходимо обновить описанным выше способом. + ### Активация выбранного устройства В зависимости от интерфейса используемой шины, периферийные устройства либо знают какой диапазон адресов им выделен (например, в интерфейсе I²C), либо нет (интерфейс APB). В первом случае, устройство понимает что к нему обратились непосредственно по адресу в данном обращении, во втором случае — по специальному сигналу. @@ -69,14 +64,14 @@ _Рисунок 1. Итоговая структура процессорной Для реализации такого кодирования достаточно выполнить сдвиг влево константы `255'd1` на значение `data_addr_o[31:24]`. -### Дополнительные правки модуля riscv_unit +### Дополнительные правки модуля processor_system -Ранее, для того чтобы ваши модули могли работать в ПЛИС, вам предоставлялся специальный модуль верхнего уровня, который выполнял всю работу по связи с периферией через входы и выходы ПЛИС. Поскольку в текущей лабораторной вы завершаете свою процессорную систему, она сама должна оказаться модулем верхнего уровня, а значит здесь вы должны и выполнить всё подключение к периферии. +Ранее, для того чтобы ваши модули могли работать в ПЛИС, вам предоставлялся специальный модуль верхнего уровня, который выполнял всю работу по связи с периферией через входы и выходы ПЛИС. Поскольку в текущей лабораторной вы завершаете свою процессорную систему, она сама должна оказаться модулем верхнего уровня, а значит здесь вам необходимо и выполнить всё подключение к периферии. -Для этого необходимо добавить в модуль `riscv_unit` дополнительные входы и выходы, которые подключены посредством файла ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)) к входам и выходам ПЛИС. +Для этого необходимо добавить в модуль `processor_system` дополнительные входы и выходы, которые подключены посредством файла ограничений к входам и выходам ПЛИС (см. документ "[Как работает ПЛИС](../../Introduction/How%20FPGA%20works.md)"). -```SystemVerilog -module riscv_unit( +```Verilog +module processor_system( input logic clk_i, input logic resetn_i, @@ -91,11 +86,11 @@ module riscv_unit( output logic [ 6:0] hex_led_o, // Вывод семисегментных индикаторов output logic [ 7:0] hex_sel_o, // Селектор семисегментных индикаторов - input logic rx_i, // Линия приема по UART + input logic rx_i, // Линия приёма по UART output logic tx_o, // Линия передачи по UART output logic [3:0] vga_r_o, // Красный канал vga - output logic [3:0] vga_g_o, // Зеленый канал vga + output logic [3:0] vga_g_o, // Зелёный канал vga output logic [3:0] vga_b_o, // Синий канал vga output logic vga_hs_o, // Линия горизонтальной синхронизации vga output logic vga_vs_o // Линия вертикальной синхронизации vga @@ -105,7 +100,7 @@ module riscv_unit( endmodule ``` -Эти порты нужно подключить к одноименным портам ваших контроллеров периферии (**речь идет только о реализуемых вами контроллерах, остальные порты должны остаться неподключенными**). Иными словами, в описании модуля должны быть все указанные входы и выходы. Но использовать вам нужно только порты, связанные с теми периферийными устройствами, реализацию которых вам необходимо подключить к процессорной системе в рамках индивидуального задания. +Эти порты нужно подключить к одноименным портам ваших контроллеров периферии (**речь идёт только о реализуемых вами контроллерах, остальные порты должны остаться неподключенными**). Иными словами, в описании модуля должны быть все указанные входы и выходы. Но использовать вам нужно только порты, связанные с теми периферийными устройствами, реализацию которых вам необходимо подключить к процессорной системе в рамках индивидуального задания. Обратите внимание на то, что изменился сигнал сброса (`resetn_i`). Буква `n` на конце означает, что сброс работает по уровню `0` (в таком случае говорят, что **активный уровень** данного сигнала `0`: когда сигнал равен нулю — это сброс, когда единице — не сброс). @@ -114,36 +109,36 @@ endmodule Для этого необходимо: 1. Подключить файл [`sys_clk_rst_gen.sv`](sys_clk_rst_gen.sv) в ваш проект. -2. Создать экземпляр этого модуля в начале описания модуля `riscv_unit` следующим образом: +2. Создать экземпляр этого модуля в начале описания модуля `processor_system` следующим образом: -```SystemVerilog +```Verilog logic sysclk, rst; sys_clk_rst_gen divider(.ex_clk_i(clk_i),.ex_areset_n_i(resetn_i),.div_i(5),.sys_clk_o(sysclk), .sys_reset_o(rst)); ``` -3. После вставки данных строк в начало описания модуля `riscv_unit` вы получите тактовый сигнал `sysclk` с частотой в 10 МГц и сигнал сброса `rst` с активным уровнем `1` (как и в предыдущих лабораторных). Все ваши внутренние модули (`riscv_core`, `data_mem` и контроллеры периферии) должны работать от тактового сигнала `sysclk`. На модули, имеющие входной сигнал сброса (`rst_i`) необходимо подать ваш сигнал `rst`. +_Листинг 1. Пример создания экземпляра блока делителя частоты._ ---- +3. После вставки данных строк в начало описания модуля `processor_system` вы получите тактовый сигнал `sysclk` с частотой в 10 МГц и сигнал сброса `rst` с активным уровнем `1` (как и в предыдущих лабораторных). Все ваши внутренние модули (`processor_core`, `data_mem` и контроллеры периферии) должны работать от тактового сигнала `sysclk`. На модули, имеющие входной сигнал сброса (`rst_i`) необходимо подать ваш сигнал `rst`. ## Задание -В рамках данной лабораторной работы необходимо реализовать модули-контроллеры двух периферийных устройств, реализующих управление в соответствии с приведенной на _рис. 2_ картой памяти и встроить их в процессорную систему, используя [_рис. 1_](../../.pic/Labs/lab_13_periph/fig_01.drawio.svg). На карте приведено семь периферийных устройств, вам необходимо взять только два из них. Какие именно — сообщит преподаватель. +В рамках данной лабораторной работы необходимо реализовать модули-контроллеры двух периферийных устройств, реализующих управление в соответствии с приведенной в _таблице 1_ картой памяти и встроить их в процессорную систему, используя [_рис. 1_](../../.pic/Labs/lab_13_periph/fig_01.drawio.svg). На карте приведено семь периферийных устройств, вам необходимо взять только два из них. Какие именно — сообщит преподаватель. -![Карта памяти](../../.pic/Labs/lab_13_periph/fig_02.png) +![../../.pic/Labs/lab_13_periph/tab_01.png](../../.pic/Labs/lab_13_periph/tab_01.png) -_Рисунок 2. Карта памяти периферийных устройств._ +_Таблица 1. Карта памяти периферийных устройств._ Работа с картой осуществляется следующим образом. Под названием каждого периферийного устройства указана старшая часть адреса (чему должны быть равны старшие 8 бит адреса, чтобы было сформировано обращение к данному периферийному устройству). Например, для переключателей это значение равно `0x01`, для светодиодов `0x02` и т.п. В самом левом столбце указаны используемые/неиспользуемые адреса в адресном пространстве данного периферийного устройства. Например для переключателей есть только один используемый адрес: `0x000000`. Его функциональное назначение и разрешения на доступ указаны в столбце соответствующего периферийного устройства. Возвращаясь к адресу `0x000000`, для переключателей мы видим следующее: -- **(R)** означает что разрешен доступ только на чтение (операция записи по этому адресу должна игнорироваться вашим контроллером). -- **"Выставленное на переключателях значение"** означает ровно то, что и означает. Если процессор выполняет операцию чтения по адресу `0x01000000` (`0x01` [старшая часть адреса переключателей] + `0x000000` [младшая часть адреса для получения выставленного на переключателях значения]), то контроллер должен выставить на выходной сигнал `RD` значение на переключателях (о том как получить это значение будет рассказано чуть позже). +- **(R)** означает что разрешён доступ только на чтение (операция записи по этому адресу должна игнорироваться вашим контроллером). +- **"Выставленное на переключателях значение"** означает ровно то, что и означает. Если процессор выполняет операцию чтения по адресу `0x01000000` (`0x01` [старшая часть адреса переключателей] + `0x000000` [младшая часть адреса для получения выставленного на переключателях значения]), то контроллер должен выставить на выходной сигнал `RD` значение на переключателях (о том, как получить это значение будет рассказано чуть позже). -Рассмотрим еще один пример. При обращении по адресу `0x02000024` (`0x02` (старшая часть адреса контроллера светодиодов) + `0x000024` (младшая часть адреса для доступа на запись к регистру сброса) ) должна произойти запись в регистр сброса, который должен сбросить значения в регистре управления зажигаемых светодиодов и регистре управления режимом "моргания" светодиодов (подробнее о том как должны работать эти регистры будет ниже). +Рассмотрим ещё один пример. При обращении по адресу `0x02000024` (`0x02` [старшая часть адреса контроллера светодиодов] + `0x000024` [младшая часть адреса для доступа на запись к регистру сброса] ) должна произойти запись в регистр сброса, который должен сбросить значения в регистре управления зажигаемых светодиодов и регистре управления режимом "моргания" светодиодов (подробнее о том как должны работать эти регистры будет ниже). Таким образом, каждый контроллер периферийного устройства должен выполнять две вещи: -1. При получении сигнала `req_i`, записать в регистр или вернуть значение из регистра, ассоциированного с переданным адресом (адрес передается с обнуленной старшей частью). Если регистра, ассоциированного с таким адресом нет (например, для переключателей не ассоциировано ни одного адреса кроме `0x000000`), игнорировать эту операцию. +1. При получении сигнала `req_i`, записать в регистр или вернуть значение из регистра, ассоциированного с переданным адресом (адрес передаётся с обнуленной старшей частью). Если регистра, ассоциированного с таким адресом нет (например, для переключателей не ассоциировано ни одного адреса кроме `0x000000`), игнорировать эту операцию. 2. Выполнять управление периферийным устройством с помощью управляющих регистров. Подробное описание периферийных устройств их управления и назначение управляющих регистров описано **после порядка выполнения задания**. @@ -152,41 +147,38 @@ _Рисунок 2. Карта памяти периферийных устрой ## Порядок выполнения задания -1. Внимательно ознакомьтесь с [примером описания модуля контроллера](../../Basic%20Verilog%20structures/Controllers.md). -2. Внимательно ознакомьтесь со спецификацией контроллеров периферии своего варианта. В случае возникновения вопросов, проконсультируйтесь с преподавателем. -3. Добавьте в проект пакет [`peripheral_pkg`](peripheral_pkg.sv). Данный пакет содержит старшие части адресов периферии в виде параметров, а также вспомогательные вызовы, используемые тестбенчем. +1. Ознакомьтесь с [примером описания модуля контроллера](../../Basic%20Verilog%20structures/Controllers.md). +2. Ознакомьтесь со спецификацией контроллеров периферии своего варианта. В случае возникновения вопросов, проконсультируйтесь с преподавателем. +3. Добавьте в проект пакет [`peripheral_pkg`](peripheral_pkg.sv). Данный пакет содержит старшие части адресов периферии в виде параметров, а также вспомогательные вызовы, используемые верификационным окружением. 4. Реализуйте модули контроллеров периферии. Имена модулей и их порты будут указаны в [описании контроллеров](#описание-контроллеров-периферийных-устройств). Пример разработки контроллера приведен в [примере описания модуля контроллера](../../Basic%20Verilog%20structures/Controllers.md). 1. Готовые модули периферии, управление которыми должны осуществлять модули-контроллеры хранятся в папке `peripheral modules`. -5. Обновите модуль `riscv_unit` в соответствии с разделом ["Дополнительные правки модуля riscv_unit"](#дополнительные-правки-модуля-riscv_unit). +5. Обновите модуль `processor_system` в соответствии с разделом ["Дополнительные правки модуля processor_system"](#дополнительные-правки-модуля-processor_system). 1. Подключите в проект файл `sys_clk_rst_gen.sv`. - 2. Добавьте в модуль `riscv_unit` входы и выходы периферии, а так же замените вход `rst_i` входом `resetn_i`. **Необходимо добавить порты даже тех периферийных устройств, которые вы не будете реализовывать**. - 3. Создайте в начале описания модуля `riscv_unit` экземпляр модуля `sys_clk_rst_gen`, скопировав приведенный фрагмент кода. - 4. Замените подключение тактового сигнала исходных подмодулей `riscv_unit` на появившийся сигнал `sysclk`. Убедитесь, что на модули, имеющие сигнал сброса, приходит сигнал `rst`. -6. Интегрируйте модули контроллеров периферии в процессорную систему по приведенной схеме руководствуясь старшими адресами контроллеров, представленными на карте памяти ([_рис. 2_](../../.pic/Labs/lab_13_periph/fig_02.png)). Это означает, что если вы реализуете контроллер светодиодов, на его вход `req_i` должна подаваться единица в случае, если `mem_req_o == 1` и старшие 8 бит адреса равны `0x02`. - 1. При интеграции вы должны подключить только модули-контроллеры вашего варианта. Контроллеры периферии других вариантов подключать не надо. - 2. Во время интеграции, вы должны использовать старшую часть адреса, представленную в карте памяти для формирования сигнала `req_i` для ваших модулей-контроллеров. + 2. Добавьте в модуль `processor_system` входы и выходы периферии, а также замените вход `rst_i` входом `resetn_i`. **Необходимо добавить порты даже тех периферийных устройств, которые вы не будете реализовывать**. + 3. Создайте в начале описания модуля `processor_system` экземпляр модуля `sys_clk_rst_gen`, скопировав фрагмент кода, приведённый в _листинге 1_. + 4. Замените подключение тактового сигнала исходных подмодулей `processor_system` на появившийся сигнал `sysclk`. Убедитесь, что на модули, имеющие сигнал сброса, приходит сигнал `rst`. +6. Интегрируйте модули контроллеров периферии в процессорную систему по схеме представленной на _рис. 1_, руководствуясь старшими адресами контроллеров, представленными на карте памяти ([_таблицы 1_](../../.pic/Labs/lab_13_periph/fig_02.png)). Это означает, что если вы реализуете контроллер светодиодов, на его вход `req_i` должна подаваться единица в случае, если `mem_req_o == 1` и старшие 8 бит адреса равны `0x02`. + 1. При интеграции вам необходимо подключить только модули-контроллеры вашего варианта. Контроллеры периферии других вариантов подключать не надо. + 2. Во время интеграции, вам необходимо использовать старшую часть адреса, представленную в карте памяти для формирования сигнала `req_i` для ваших модулей-контроллеров. 7. Проверьте работу процессорной системы с помощью моделирования. 1. Для моделирования используйте тестбенч `lab_13_tb_system`. - 2. Для каждой пары контроллеров в папке `firmware/mem_files` представлены файлы, инициализирующие память инструкций. Обратите внимание, что для пары "PS2-VGA" также необходим файл, инициализирующий память данных (в модуле `data_mem` необходимо добавить вызов инициализирующей функции `$readmemh` в блоке `initial`). + 2. Для каждой пары контроллеров в папке `firmware/mem_files` представлены файлы, инициализирующие память инструкций. Содержимым одного из файлов, соответствующих паре периферийных устройств вашего варианта необходимо заменить содержимое файла `program.mem` в `Design Sources` проекта. Обратите внимание, что для пары "PS2-VGA" также необходим файл, инициализирующий память данных (в модуле `data_mem` необходимо добавить вызов инициализирующей функции `$readmemh` в блоке `initial`). 3. Для проверки тестбенч имитирует генерацию данных периферийных устройств ввода. Перед проверкой желательно найти в тестбенче `initial`-блок своего устройства ввода (`sw_block`, `ps2_block`, `uart_block`) — по этому блоку будет понятно, какие данные будет передавать устройство ввода. Именно эти данные в итоге должны оказаться на шине `mem_rd_i`. 4. Для того, чтобы понять, что устройство работает должным образом, в первую очередь необходимо убедиться, что контроллер устройства ввода успешно осуществил прием данных (сгенерированные тестбенчем данные оказались в соответствующем регистре контроллера периферийного устройства) и выполнил запрос на прерывание. 5. После чего, необходимо убедиться, что процессор среагировал на данное прерывание, и в процессе его обработки в контроллер устройства вывода были поданы выходные данные. 6. Для того, чтобы лучше понимать как именно процессор будет обрабатывать прерывание, рекомендуется ознакомиться с исходным кодом исполняемой программы, расположенным в папке `firmware/software`. - 1. Общая логика программ для всех периферий сводится к ожиданию в бесконечном цикле прерывания от устройства ввода, после чего в процессе обработки прерывания процессор загружает данные от устройства ввода и (возможно преобразовав их) выдает их на устройство вывода. + 1. Общая логика программ для всех периферий сводится к ожиданию в бесконечном цикле прерывания от устройства ввода, после чего в процессе обработки прерывания процессор загружает данные от устройства ввода и (возможно преобразовав их) выдаёт их на устройство вывода. 2. В случае правильной работы программы на временной диаграмме это будет отображено следующим образом: сразу после поступления прерывания от устройства ввода, на системной шине начинается операция чтения из устройства ввода (это легко определить по старшей части адреса, к которому обращается процессор), после чего выполняются операции записи в устройство вывода (аналогично, обращение к устройству вывода можно определить по адресу, к которому обращается процессор). - 7. При моделировании светодиодов лучше уменьшить значение, до которого считает счетчик в режиме "моргания" в 1000 раз, чтобы сократить время моделирования до очередного переключения светодиодов. Перед генерацией битстрима это значение будет необходимо восстановить, иначе моргание станет слишком быстрым и его нельзя будет воспринять невооруженным взглядом. + 7. При моделировании светодиодов лучше уменьшить значение, до которого считает счётчик в режиме "моргания" в 1000 раз, чтобы сократить время моделирования до очередного переключения светодиодов. Перед генерацией битстрима это значение будет необходимо восстановить, иначе моргание станет слишком быстрым и его нельзя будет воспринять невооружённым взглядом. 8. Переходить к следующему пункту можно только после того, как вы полностью убедились в работоспособности модуля на этапе моделирования (увидели корректные значения на выходных сигналах периферии, либо (если по сигналам периферии сложно судить о работоспособности), значениям в контрольных/статусных регистрах модуля-контроллера этой периферии). Генерация битстрима будет занимать у вас долгое время, а итогом вы получите результат: заработало / не заработало, без какой-либо дополнительной информации, поэтому без прочного фундамента на моделировании далеко уехать у вас не выйдет. - -9. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе. +9. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)), если тот ещё не был подключён, либо замените его содержимое данными из файла к этой лабораторной работе. 10. Проверьте работу вашей процессорной системы на отладочном стенде с ПЛИС. - 1. Обратите внимание, что в данной лабораторной уже не будет модуля верхнего уровня `nexys_...`, так как ваш модуль процессорной системы уже полностью самостоятелен и взаимодействует непосредственно с ножками ПЛИС через модули, управляемые контроллерами периферии. - 2. Для проверки периферии переключателей и светодиодов будет достаточно одного лишь отладочного стенда. Для проверки всей остальной периферии может могут потребоваться: компьютер (для uart_rx / uart_tx), клавиатура (для контролера клавиатуры) и VGA-монитор для VGA-контроллера. - 1. Чтобы проверить работоспособность контроллеров UART, необходимо запустить на компьютере программу Putty, в настройках программы указать настройки, которыми будет сконфигурирован программой ваш контроллер (либо указать значения, которыми сбрасываются регистры, если программа ничего не настраивает) и COM-порт, через который компьютер будет общаться с контроллером. Определить нужный COM-порт на операционной системе Windows можно через "Диспетчер устройств", который можно открыть через меню пуск. - В данном окне необходимо найти вкладку "Порты (COM и LPT)", раскрыть ее, а затем подключить отладочный стенд через USB-порт (если тот еще не был подключен). В списке появится новое устройство, а в скобках будет указан нужный COM-порт. - 2. Не смотря на то, что описанный контроллер клавиатуры позволяет управлять клавиатурой с интерфейсом PS/2, некоторые платы (например Nexys A7) позволяют подключать вместо них клавиатуры с USB-интерфейсом. Дело в том, что PS/2 уже давно устарел и найти клавиатуры с таким интерфейсом — задача непростая. Однако протокол передачи по этому интерфейсу очень удобен для образовательны целей, поэтому некоторые производители просто ставят на платы переходник с USB на PS/2, позволяя объединить простоту разработки с удобством использования. - ---- + 1. Обратите внимание, что в данной лабораторной уже не будет модуля верхнего уровня `nexys_...`, так как ваш модуль процессорной системы уже полностью самостоятелен и взаимодействует непосредственно с ножками ПЛИС через модули, управляемые контроллерами периферии. + 2. Для проверки периферии переключателей и светодиодов будет достаточно одного лишь отладочного стенда. Для проверки всей остальной периферии может могут потребоваться: компьютер (для uart_rx / uart_tx), клавиатура (для контроллера клавиатуры) и VGA-монитор для VGA-контроллера. + 1. Чтобы проверить работоспособность контроллеров UART, необходимо запустить на компьютере программу Putty, в настройках программы указать настройки, которыми будет сконфигурирован программой ваш контроллер (либо указать значения, которыми сбрасываются регистры, если программа ничего не настраивает) и COM-порт, через который компьютер будет общаться с контроллером. Определить нужный COM-порт на операционной системе Windows можно через "Диспетчер устройств", который можно открыть через меню пуск. + В данном окне необходимо найти вкладку "Порты (COM и LPT)", раскрыть её, а затем подключить отладочный стенд через USB-порт (если тот ещё не был подключён). В списке появится новое устройство, а в скобках будет указан нужный COM-порт. + 2. Несмотря на то, что описанный контроллер клавиатуры позволяет управлять клавиатурой с интерфейсом PS/2, некоторые платы (например, Nexys A7) позволяют подключать вместо них клавиатуры с USB-интерфейсом. Дело в том, что PS/2 уже давно устарел и найти клавиатуры с таким интерфейсом — задача непростая. Однако протокол передачи по этому интерфейсу очень удобен для образовательных целей, поэтому некоторые производители просто ставят на платы переходник с USB на PS/2, позволяя объединить простоту разработки с удобством использования. ## Описание контроллеров периферийных устройств @@ -203,7 +195,7 @@ _Рисунок 2. Карта памяти периферийных устрой 3. На входе `write_enable_i` выставлено значение `0`. 4. На входе `addr_i` выставлено значение `0xАДРЕС` -Обратите внимание на то, что **запрос на чтение** должен обрабатываться **синхронно** (выходные данные должны выдаваться по положительному фронту `clk_i`) так же как был реализован порт на чтение памяти данных в [ЛР№6](../06.%20Main%20memory/). +Обратите внимание на то, что **запрос на чтение** должен обрабатываться **синхронно** (выходные данные должны выдаваться по положительному фронту `clk_i`) так же, как был реализован порт на чтение памяти данных в [ЛР№6](../06.%20Main%20memory/). При описании поддерживаемых режимов доступа по данному адресу используются следующее обозначения: @@ -213,7 +205,7 @@ _Рисунок 2. Карта памяти периферийных устрой В случае отсутствия **запроса на чтение**, на выходе `read_data_o` не должно меняться значение (тоже самое было сделано в процессе разработки памяти данных). -Если пришел **запрос на запись** или **чтение**, это еще не значит, что контроллер должен его выполнить. В случае, если запрос происходит по адресу, не поддерживающему этот запрос (например **запрос на запись** по адресу, поддерживающему только чтение), данный запрос должен игнорироваться. В случае **запроса на чтение** по недоступному адресу, на выходе `read_data_o` должно остаться прежнее значение. +Если пришёл **запрос на запись** или **чтение**, это ещё не значит, что контроллер должен его выполнить. В случае, если запрос происходит по адресу, не поддерживающему этот запрос (например **запрос на запись** по адресу, поддерживающему только чтение), данный запрос должен игнорироваться. В случае **запроса на чтение** по недоступному адресу, на выходе `read_data_o` должно остаться прежнее значение. В случае осуществления записи по принятому запросу, необходимо записать данные с сигнала `write_data_i` в регистр, ассоциированный с адресом `addr_i` (если разрядность регистра меньше разрядности сигнала `write_data_i`, старшие биты записываемых данных отбрасываются). @@ -223,7 +215,7 @@ _Рисунок 2. Карта памяти периферийных устрой Переключатели являются простейшим устройством ввода на отладочном стенде `Nexys A7`. Соответственно и контроллер, осуществляющий доступ процессора к ним так же будет очень простым. Рассмотрим прототип модуля, который вам необходимо реализовать: -```SystemVerilog +```Verilog module sw_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к системной шине @@ -256,12 +248,14 @@ endmodule По сути, логика работы контроллера сводится к тому, выдавать на шину `read_data_o` данные со входа `sw_i` каждый раз, когда приходит **запрос на чтение** по нулевому адресу. Поскольку разрядность `sw_i` в два раза меньше разрядности выхода `read_data_o` его старшие биты необходимо дополнить нулями. -Адресное пространство контроллера: +Адресное пространство контроллера представлено в _таблице 2_. |Адрес|Режим доступа| Функциональное назначение | |-----|-------------|-------------------------------------------------| |0x00 | R | Чтение значения, выставленного на переключателях| +_Таблица 2. Адресное пространство контроллера переключателей._ + При этом, будучи устройством ввода, данный модуль может генерировать прерывание, чтобы сообщить процессору о том, что данные на переключателях были изменены. Если на очередном такте `clk_i` данные на входе `sw_i` изменились (т.е. отличаются от тех, что были на предыдущем такте), модуль должен выставить значение `1` на выходе `interrupt_request_o` и удерживать его до получения сигнала о завершении обработки прерывания `interrupt_return_i`. Для отслеживания изменений на входе `sw_i` между тактами синхроимпульса вам потребуется вспомогательный регистр, каждый такт сохраняющий значение `sw_i`. При реализации данного регистра, не забывайте о том, что его необходимо сбрасывать посредством сигнала `rst_i`. @@ -271,7 +265,7 @@ endmodule Как и переключатели, светодиоды являются простейшим устройством вывода. Поэтому, чтобы задание было интересней, для их управления был добавлен регистр, управляющий режимом вывода данных на светодиоды. Рассмотрим прототип модуля, который вам необходимо реализовать: -```SystemVerilog +```Verilog module led_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к системной шине @@ -300,13 +294,13 @@ endmodule Регистр `led_mode` отвечает за режим вывода данных на светодиоды. Когда этот регистр равен единице, светодиоды должны "моргать" выводимым значением. Под морганием подразумевается вывод значения из регистра `led_val` на выход `led_o` на одну секунду (загорится часть светодиодов, соответствующие которым биты шины `led_o` равны единице), после чего на одну секунду выход `led_o` необходимо подать нули. Запись и чтение регистра `led_mode` осуществляется по адресу `0x04`. -Отсчет времени можно реализовать простейшим счетчиком, каждый такт увеличивающимся на 1 и сбрасывающимся по достижении определенного значения, чтобы продолжить считать с нуля. Зная тактовую частоту, нетрудно определить до скольки должен считать счетчик. При тактовой частоте в 10 МГц происходит 10 миллионов тактов в секунду. Это означает, что при такой тактовой частоте через секунду счетчик будет равен `10⁷-1` (счет идет с нуля). Тем не менее удобней будет считать не до `10⁷-1` (что было бы достаточно очевидным и тоже правильным решением), а до `2*10⁷-1`. В этом случае старший бит счетчика каждую секунду будет инвертировать свое значение, что может быть использовано при реализации логики "моргания". +Отсчёт времени можно реализовать простейшим счётчиком, каждый такт увеличивающимся на 1 и сбрасывающимся по достижении определенного значения, чтобы продолжить считать с нуля. Зная тактовую частоту, нетрудно определить до скольки должен считать счётчик. При тактовой частоте в 10 МГц происходит 10 миллионов тактов в секунду. Это означает, что при такой тактовой частоте через секунду счётчик будет равен `10⁷-1` (счёт идёт с нуля). Тем не менее удобней будет считать не до `10⁷-1` (что было бы достаточно очевидным и тоже правильным решением), а до `2*10⁷-1`. В этом случае старший бит счётчика каждую секунду будет инвертировать своё значение, что может быть использовано при реализации логики "моргания". -Важно отметить, что счетчик должен работать только при `led_mode == 1`, в противном случае счетчик должен быть равен нулю. +Важно отметить, что счётчик должен работать только при `led_mode == 1`, в противном случае счётчик должен быть равен нулю. -Обратите внимание на то, что адрес `0x24` является адресом сброса. В случае **запроса на запись** по этому адресу значения `1`. вы должны сбросить регистры `led_val`, `led_mode` и все вспомогательные регистры, которые вы создали. Для реализации сброса вы можете как создать отдельный регистр `led_rst`, в который будет происходить запись, а сам сброс будет происходить по появлению единицы в этом регистре (в этом случае необходимо не забыть сбрасывать и этот регистр тоже), так и создать обычный провод, формирующий единицу в случае выполнения всех указанных условий (условий **запроса на запись**, адреса сброса и значения записываемых данных равному единице). +Обратите внимание на то, что адрес `0x24` является адресом сброса. В случае **запроса на запись** по этому адресу значения `1`. вам необходимо сбросить регистры `led_val`, `led_mode` и все вспомогательные регистры, которые вы создали. Для реализации сброса вы можете как создать отдельный регистр `led_rst`, в который будет происходить запись, а сам сброс будет происходить по появлению единицы в этом регистре (в этом случае необходимо не забыть сбрасывать и этот регистр тоже), так и создать обычный провод, формирующий единицу в случае выполнения всех указанных условий (условий **запроса на запись**, адреса сброса и значения записываемых данных равному единице). -Адресное пространство контроллера: +Адресное пространство контроллера представлено в _таблице 3_. |Адрес|Режим доступа|Допустимые значения| Функциональное назначение | |-----|-------------|-------------------|-----------------------------------------------------------------------------------| @@ -314,13 +308,15 @@ endmodule |0x04 | RW | [0:1] | Чтение и запись в регистр `led_mode`, отвечающий за режим "моргания" светодиодами | |0x24 | W | 1 | Запись сигнала сброса | +Таблица 3. Адресное пространство контроллера светодиодов. + ### Клавиатура PS/2 Клавиатура [PS/2](https://ru.wikipedia.org/wiki/PS/2_(порт)) осуществляет передачу [скан-кодов](https://ru.wikipedia.org/wiki/Скан-код), нажатых на этой клавиатуре клавиш. -В рамках данной лабораторной работы вам будет дан готовый модуль, осуществляющий прием данных с клавиатуры. От вас требуется написать лишь модуль, осуществляющий контроль предоставленным модулем. У готового модуля будет следующий прототип: +В рамках данной лабораторной работы вам будет дан готовый модуль, осуществляющий приём данных с клавиатуры. От вас требуется написать лишь модуль, осуществляющий контроль предоставленным модулем. У готового модуля будет следующий прототип: -```SystemVerilog +```Verilog module PS2Receiver( input clk_i, // Сигнал тактирования input rst_i, // Сигнал сброса @@ -334,7 +330,7 @@ endmodule Вам необходимо реализовать модуль-контроллер со следующим прототипом: -```SystemVerilog +```Verilog module ps2_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к системной шине @@ -357,7 +353,7 @@ module ps2_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к модулю, - осуществляющему прием данных с клавиатуры + осуществляющему приём данных с клавиатуры */ input logic kclk_i, input logic kdata_i @@ -369,11 +365,11 @@ logic scan_code_is_unread; endmodule ``` -В первую очередь, вы должны создать экземпляр модуля `PS2Receiver` внутри вашего модуля-контроллера, соединив соответствующие входы. Для подключения к выходам необходимо создать дополнительные провода. +В первую очередь, вам необходимо создать экземпляр модуля `PS2Receiver` внутри вашего модуля-контроллера, соединив соответствующие входы. Для подключения к выходам необходимо создать дополнительные провода. -По каждому восходящему фронту сигнала `clk_i` вы должны проверять выход `keycode_valid_o` и, если тот равен единице, записать значение с выхода `keycode_o` в регистр `scan_code`. При этом значение регистра `scan_code_is_unread` необходимо выставить в единицу. +По каждому восходящему фронту сигнала `clk_i` вам необходимо проверять выход `keycode_valid_o` и, если тот равен единице, записать значение с выхода `keycode_o` в регистр `scan_code`. При этом значение регистра `scan_code_is_unread` необходимо выставить в единицу. -В случае, если произошел **запрос на чтение** по адресу `0x00`, необходимо выставить на выход `read_data_o` значение регистра `scan_code` (дополнив старшие биты нулями), при этом значение регистра `scan_code_is_unread` необходимо обнулить. В случае, если одновременно с **запросом на чтение** пришел сигнал `keycode_valid_o`, регистр `scan_code_is_unread` обнулять не нужно (в этот момент в регистр `scan_code` уже записывается новое, еще непрочитанное значение). +В случае, если произошёл **запрос на чтение** по адресу `0x00`, необходимо выставить на выход `read_data_o` значение регистра `scan_code` (дополнив старшие биты нулями), при этом значение регистра `scan_code_is_unread` необходимо обнулить. В случае, если одновременно с **запросом на чтение** пришёл сигнал `keycode_valid_o`, регистр `scan_code_is_unread` обнулять не нужно (в этот момент в регистр `scan_code` уже записывается новое, ещё непрочитанное значение). Обнуление регистра `scan_code_is_unread` должно происходить и в случае получения сигнала `interrupt_return_i` (однако опять же, если в этот момент приходит сигнал `keycode_valid_o`, обнулять регистр не нужно). @@ -381,9 +377,9 @@ endmodule В случае **запроса на запись** значения `1` по адресу `0x24`, необходимо осуществить сброс регистров `scan_code` и `scan_code_is_unread` в `0`. -Регистр `scan_code_is_unread` необходимо подключить к выходу `interrupt_request_o`. Таким образом процессор может узнавать о нажатых клавишах как посредством программного опроса путем чтения значения регистра`scan_code_is_unread`, так и посредством прерываний через сигнал`interrupt_request_o`. +Регистр `scan_code_is_unread` необходимо подключить к выходу `interrupt_request_o`. Таким образом процессор может узнавать о нажатых клавишах как посредством программного опроса путём чтения значения регистра `scan_code_is_unread`, так и посредством прерываний через сигнал `interrupt_request_o`. -Адресное пространство контроллера: +Адресное пространство контроллера представлено в _таблице 4_. |Адрес|Режим доступа|Допустимые значения| Функциональное назначение | |-----|-------------|-------------------|-------------------------------------------------------------------------------------------------------------------| @@ -391,11 +387,13 @@ endmodule |0x04 | R | [0:1] | Чтение из регистра `scan_code_is_unread`, сообщающего о том, что есть непрочитанные данные в регистре `scan_code` | |0x24 | W | 1 | Запись сигнала сброса | +_Таблица 4. Адресное пространство контроллера клавиатуры._ + ### Семисегментные индикаторы Семисегментные индикаторы позволяют выводить арабские цифры и первые шесть букв латинского алфавита, тем самым позволяя отображать шестнадцатеричные цифры. На отладочном стенде `Nexys A7` размещено восемь семисегментных индикаторов. Для вывода цифр на эти индикаторы, вам будет предоставлен модуль `hex_digits`, вам нужно лишь написать модуль, осуществляющий контроль над ним. Прототип модуля `hex_digits` следующий: -```SystemVerilog +```Verilog module hex_digits( input logic clk_i, input logic rst_i, @@ -403,7 +401,7 @@ module hex_digits( input logic [3:0] hex1_i, // Цифра, выводимая на первый индикатор input logic [3:0] hex2_i, // Цифра, выводимая на второй индикатор input logic [3:0] hex3_i, // Цифра, выводимая на третий индикатор - input logic [3:0] hex4_i, // Цифра, выводимая на четвертый индикатор + input logic [3:0] hex4_i, // Цифра, выводимая на четвёртый индикатор input logic [3:0] hex5_i, // Цифра, выводимая на пятый индикатор input logic [3:0] hex6_i, // Цифра, выводимая на шестой индикатор input logic [3:0] hex7_i, // Цифра, выводимая на седьмой индикатор @@ -422,11 +420,11 @@ endmodule За включение/отключение индикаторов отвечает входной сигнал `bitmask_i`, состоящий из 8 бит, каждый из которых включает/отключает соответствующий индикатор. Например, при `bitmask_i == 8'b0000_0101`, включены будут нулевой и второй индикаторы, остальные будут погашены. -Выходные сигналы `hex_led` и `hex_sel` необходимо просто подключить к соответствующим выходным сигналам модуля-контроллера. Они пойдут на выходы ПЛИС, соединенные с семисегментными индикаторами. +Выходные сигналы `hex_led` и `hex_sel` необходимо просто подключить к соответствующим выходным сигналам модуля-контроллера. Они пойдут на выходы ПЛИС, соединённые с семисегментными индикаторами. Для управления данным модулем, необходимо написать модуль-контроллер со следующим прототипом: -```SystemVerilog +```Verilog module hex_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к системной шине @@ -451,7 +449,7 @@ module hex_sb_ctrl( endmodule ``` -Регистры `hex0-hex7` отвечают за вывод цифры на соответствующий семисегментный индикатор. Регистр `bitmask` отвечает за включение/отключение семисегментных индикаторов. Когда в регистре `bitmask` бит, индекс которого совпадает с номером индикатора равен единице — тот включен и выводит число, совпадающее со значением в соответствующем регистре `hex0-hex7`. Когда бит равен нулю — этот индикатор гаснет. +Регистры `hex0-hex7` отвечают за вывод цифры на соответствующий семисегментный индикатор. Регистр `bitmask` отвечает за включение/отключение семисегментных индикаторов. Когда в регистре `bitmask` бит, индекс которого совпадает с номером индикатора равен единице — тот включён и выводит число, совпадающее со значением в соответствующем регистре `hex0-hex7`. Когда бит равен нулю — этот индикатор гаснет. Доступ на чтение/запись регистров `hex0-hex7` осуществляется по адресам `0x00-0x1c` (см. таблицу адресного пространства). @@ -459,7 +457,7 @@ endmodule При **запросе на запись** единицы по адресу `0x24` необходимо выполнить сброс всех регистров. При этом регистр `bitmask` должен сброситься в значение `0xFF` (т.е. после сброса все семисегментные индикаторы должны загореться с цифрой `0`). -Адресное пространство контроллера: +Адресное пространство контроллера представлено в _таблице 5_. |Адрес|Режим доступа|Допустимые значения| Функциональное назначение | |-----|-------------|-------------------|---------------------------------------------------------| @@ -474,44 +472,46 @@ endmodule |0x20 | RW | [0:255] | Регистр, управляющий включением/отключением индикаторов | |0x24 | W | 1 | Запись сигнала сброса | +_Таблица 5. Адресное пространство контроллера семисегментных индикаторов._ + ### UART -[UART](https://ru.wikipedia.org/wiki/Универсальный_асинхронный_приёмопередатчик) — это последовательный интерфейс, использующий для приема и передачи данных по одной независимой линии с поддержкой контроля целостности данных. +[UART](https://ru.wikipedia.org/wiki/Универсальный_асинхронный_приёмопередатчик) — это последовательный интерфейс, использующий для приёма и передачи данных по одной независимой линии с поддержкой контроля целостности данных. -Для того, чтобы передача данных была успешно осуществлена, приемник и передатчик на обоих концах одного провода должны договориться о параметрах передачи: +Для того, чтобы передача данных была успешно осуществлена, приёмник и передатчик на обоих концах одного провода должны договориться о параметрах передачи: - её скорости (бодрейт); -- контроля целостности данных (использовать или нет [бит четности](https://en.wikipedia.org/wiki/Parity_bit)); +- контроля целостности данных (использовать или нет [бит чётности](https://en.wikipedia.org/wiki/Parity_bit)); - длины стопового бита. -Вам будут предоставлены модули, осуществляющие прием и передачу данных по этому интерфейсу, от вас лишь требуется написать модули, осуществляющие управление предоставленными модулями. +Вам будут предоставлены модули, осуществляющие приём и передачу данных по этому интерфейсу, от вас лишь требуется написать модули, осуществляющие управление предоставленными модулями. -```SystemVerilog +```Verilog module uart_rx ( input logic clk_i, // Тактирующий сигнал input logic rst_i, // Сигнал сброса - input logic rx_i, // Сигнал линии, подключенной к выводу ПЛИС, + input logic rx_i, // Сигнал линии, подключённой к выводу ПЛИС, // по которой будут приниматься данные - output logic busy_o, // Сигнал о том, что модуль занят приемом данных + output logic busy_o, // Сигнал о том, что модуль занят приёмом данных input logic [16:0] baudrate_i, // Настройка скорости передачи данных - input logic parity_en_i,// Настройка контроля целостности через бит четности + input logic parity_en_i,// Настройка контроля целостности через бит чётности input logic [1:0] stopbit_i, // Настройка длины стопового бита output logic [7:0] rx_data_o, // Принятые данные - output logic rx_valid_o // Сигнал о том, что прием данных завершен + output logic rx_valid_o // Сигнал о том, что прием данных завершён ); endmodule ``` -```SystemVerilog +```Verilog module uart_tx ( input logic clk_i, // Тактирующий сигнал input logic rst_i, // Сигнал сброса - output logic tx_o, // Сигнал линии, подключенной к выводу ПЛИС, + output logic tx_o, // Сигнал линии, подключённой к выводу ПЛИС, // по которой будут отправляться данные output logic busy_o, // Сигнал о том, что модуль занят передачей данных input logic [16:0] baudrate_i, // Настройка скорости передачи данных - input logic parity_en_i,// Настройка контроля целостности через бит четности + input logic parity_en_i,// Настройка контроля целостности через бит чётности input logic [1:0] stopbit_i, // Настройка длины стопового бита input logic [7:0] tx_data_i, // Отправляемые данные input logic tx_valid_i // Сигнал о старте передачи данных @@ -521,7 +521,7 @@ endmodule Для управления этими модулями вам необходимо написать два модуля-контроллера со следующими прототипами -```SystemVerilog +```Verilog module uart_rx_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к системной шине @@ -559,7 +559,7 @@ module uart_rx_sb_ctrl( endmodule ``` -```SystemVerilog +```Verilog module uart_tx_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к системной шине @@ -606,13 +606,13 @@ endmodule Доступ на запись в регистр `data` модуля `uart_tx_sb_ctrl` происходит по адресу `0x00` в моменты положительного фронта `clk_i`, когда сигнал `busy_o` равен нулю. Доступ на чтение этого регистра может осуществляться в любой момент времени. -На вход `tx_data_i` модуля `uart_tx` непрерывно подается младший байт входа `write_data_i`. +На вход `tx_data_i` модуля `uart_tx` непрерывно подаётся младший байт входа `write_data_i`. -На вход `tx_valid_i` модуля `uart_tx` подается единица в момент выполнения **запроса на запись** по адресу `0x00` (при сигнале `busy` равном нулю). В остальное время на вход этого сигнала подается `0`. +На вход `tx_valid_i` модуля `uart_tx` подаётся единица в момент выполнения **запроса на запись** по адресу `0x00` (при сигнале `busy` равном нулю). В остальное время на вход этого сигнала подаётся `0`. В случае **запроса на запись** значения `1` по адресу `0x24` (адресу сброса), все регистры модуля-контроллера должны сброситься. При этом регистр `baudrate` должен принять значение `9600`, регистр, `stopbit` должен принять значение `1`. Остальные регистры должны принять значение `0`. -Адресное пространство контроллера `uart_rx_sb_ctrl`: +Адресное пространство контроллера `uart_rx_sb_ctrl` представлено в _таблице 6_. |Адрес|Режим доступа|Допустимые значения| Функциональное назначение | |-----|-------------|-------------------|------------------------------------------------------------------------------------------------------------| @@ -620,22 +620,26 @@ endmodule |0x04 | R | [0:1] | Чтение из регистра `valid`, сообщающего о том, что есть непрочитанные данные в регистре `data` | |0x08 | R | [0:1] | Чтение из регистра `busy`, сообщающего о том, что модуль находится в процессе приема данных | |0x0C | RW | [0:131072] | Чтение/запись регистра `baudrate`, отвечающего за скорость передачи данных | -|0x10 | RW | [0:1] | Чтение/запись регистра `parity_en`, отвечающего за включение отключение проверки данных через бит четности | +|0x10 | RW | [0:1] | Чтение/запись регистра `parity_en`, отвечающего за включение отключение проверки данных через бит чётности | |0x14 | RW | [1:2] | Чтение/запись регистра `stopbit`, хранящего длину стопового бита | |0x24 | W | 1 | Запись сигнала сброса | -Адресное пространство контроллера `uart_tx_sb_ctrl`: +_Таблица 6. Адресное пространство приёмника UART._ + +Адресное пространство контроллера `uart_tx_sb_ctrl` представлено в _таблице 7_. |Адрес|Режим доступа|Допустимые значения| Функциональное назначение | |-----|-------------|-------------------|------------------------------------------------------------------------------------------------------------| |0x00 | RW | [0:255] | Чтение и запись регистра `data`, хранящего значение отправляемых данных | |0x08 | R | [0:1] | Чтение из регистра `busy`, сообщающего о том, что модуль находится в процессе передачи данных | |0x0C | RW | [0:131072] | Чтение/запись регистра `baudrate`, отвечающего за скорость передачи данных | -|0x10 | RW | [0:1] | Чтение/запись регистра `parity_en`, отвечающего за включение отключение проверки данных через бит четности | +|0x10 | RW | [0:1] | Чтение/запись регистра `parity_en`, отвечающего за включение отключение проверки данных через бит чётности | |0x14 | RW | [1:2] | Чтение/запись регистра `stopbit`, хранящего длину стопового бита | |0x24 | W | 1 | Запись сигнала сброса | -В случае установки регистра `parity_en` в значение `1`, модуль uart_tx будет дополнять посылку битом четности (который вычисляется как исключающее ИЛИ по всем битам передаваемого байта). Модуль `uart_rx` же будет выполнять проверку этого бита с тем, что он рассчитает самостоятельно. Однако в случае появления ошибки, внешне его поведение никак не изменится (поскольку выход `err_o` данного модуля закомментирован ради простоты системы). +_Таблица 7. Адресное пространство передатчика UART._ + +В случае установки регистра `parity_en` в значение `1`, модуль uart_tx будет дополнять посылку битом чётности (который вычисляется как исключающее ИЛИ по всем битам передаваемого байта). Модуль `uart_rx` же будет выполнять проверку этого бита с тем, что он рассчитает самостоятельно. Однако в случае появления ошибки, внешне его поведение никак не изменится (поскольку выход `err_o` данного модуля закомментирован ради простоты системы). ### Видеоадаптер @@ -643,17 +647,17 @@ endmodule ![https://upload.wikimedia.org/wikipedia/commons/c/cf/RebelstarII.png](https://upload.wikimedia.org/wikipedia/commons/c/cf/RebelstarII.png) -_Рисунок 3. Пример игры с использованием символьной графики[[2]](https://en.wikipedia.org/wiki/Rebelstar)._ +_Рисунок 2. Пример игры с использованием символьной графики[[2]](https://en.wikipedia.org/wiki/Rebelstar)._ -Для управления выводимым на экран содержимым, адресное пространство модуля разделено на следующие диапазоны: +Для управления выводимым на экран содержимым, адресное пространство модуля разделено на диапазоны, представленные в _таблице 8_. -![../../.pic/Labs/lab_13_periph/fig_04.png](../../.pic/Labs/lab_13_periph/fig_04.png) +![../../.pic/Labs/lab_13_periph/tab_08.png](../../.pic/Labs/lab_13_periph/tab_08.png) -_Рисунок 4. Карта памяти vga-модуля._ +_Таблица 8. Адресное пространство контроллера VGA._ -Для того, чтобы вывести символ на экран, необходимо использовать адрес этого символа на сетке `80x30` (диапазон адресов `char_map`). К примеру, мы хотим вывести символ в верхнем левом углу (т.е. нулевой символ нулевой строки). Это нулевой символ в диапазоне адресов `char_map`. Поскольку данный диапазон начинается с адреса `0x0000_0000`, запись по этому адресу приведет к отображению символа, соответствующего [ASCII-коду](https://www.asciitable.com/), пришедшему на `write_data_i`. +Для того, чтобы вывести символ на экран, необходимо использовать адрес этого символа на сетке `80x30` (диапазон адресов `char_map`). К примеру, мы хотим вывести символ в верхнем левом углу (т.е. нулевой символ нулевой строки). Это нулевой символ в диапазоне адресов `char_map`. Поскольку данный диапазон начинается с адреса `0x0000_0000`, запись по этому адресу приведёт к отображению символа, соответствующего [ASCII-коду](https://www.asciitable.com/), пришедшему на `write_data_i`. -Если мы хотим вывести нулевой (левый) символ в первой строке (счет ведется с нуля), то необходимо произвести запись по адресу `1*80 + 0 = 80 = 0x0000_0050`. +Если мы хотим вывести нулевой (левый) символ в первой строке (счёт ведётся с нуля), то необходимо произвести запись по адресу `1*80 + 0 = 80 = 0x0000_0050`. Вывод символа в правом нижнем углу осуществляется записью по адресу `0x0000_095F` (80*30-1) @@ -665,17 +669,17 @@ _Рисунок 4. Карта памяти vga-модуля._ Цветовая схема каждой позиции состоит из двух цветов: цвета фона и цвета символа. Оба эти цвета выбираются из палитры 8 цветов, каждый из которых содержит два оттенка: цвет на полной яркости и цвет на уменьшенной яркости (см. _рис. 5_). Один из цветов — черный, оба его оттенка представляют собой один и тот же цвет. На _рис. 5_ приведены коды цветов их rgb-значения: -![../../.pic/Labs/lab_13_periph/fig_05.png](../../.pic/Labs/lab_13_periph/fig_05.png) +![../../.pic/Labs/lab_13_periph/fig_03.png](../../.pic/Labs/lab_13_periph/fig_03.png) -_Рисунок 5. Цветовая палитра vga-модуля._ +_Рисунок 3. Цветовая палитра vga-модуля._ Код цвета формируется следующим образом: старший бит определяет яркость оттенка цвета. Оставшиеся 3 бита кодируют используемый канал: - 0 бит кодирует использование синего канала; -- 1 бит кодирует использование зеленого канала; +- 1 бит кодирует использование зелёного канала; - 2 бит кодирует использование красного канала. -Таким образом, для установки цветовой схемы, необходимо выбрать два цвета из палитры, склеить их (в старших разрядах идет цвет символа, в младших — цвет фона) и записать получившееся 8-битное значение по адресу выбранной позиции в диапазоне адресов цветовой схемы (color_map). +Таким образом, для установки цветовой схемы, необходимо выбрать два цвета из палитры, склеить их (в старших разрядах идёт цвет символа, в младших — цвет фона) и записать получившееся 8-битное значение по адресу выбранной позиции в диапазоне адресов цветовой схемы (color_map). К примеру, мы хотим установить черный фоновый цвет и белый цвет в качестве цвета символа для верхней левой позиции. В этом случае, мы должны записать значение `f0` (f(15) — код белого цвета, 0 — код черного цвета) по адресу `0x0000_1000` (нулевой адрес в диапазоне `color_map`). @@ -683,15 +687,15 @@ _Рисунок 5. Цветовая палитра vga-модуля._ Допустим, нам необходимо отрисовать символ `F` (ascii-код `0x46`). -![../../.pic/Labs/lab_13_periph/fig_06.png](../../.pic/Labs/lab_13_periph/fig_06.png) +![../../.pic/Labs/lab_13_periph/fig_04.png](../../.pic/Labs/lab_13_periph/fig_04.png) -_Рисунок 6. Отрисовка символа `F` в разрешении 16х8 пикселей._ +_Рисунок 4. Отрисовка символа `F` в разрешении 16х8 пикселей._ Данный символ состоит из 16 строчек по 8 пикселей. Каждый пиксель кодируется одним битом (горит/не горит, цвет символа/фоновый цвет). Каждая строчка кодируется одним байтом (8 бит на 8 пикселей). Таким образом, каждый сканкод требует 16 байт памяти. Данный модуль поддерживает 256 сканкодов. Следовательно, для хранения шрифта под каждый из 256 сканкодов требуется 16 * 256 = 4KiB памяти. -Для хранения шрифтов в модуле отведен диапазон адресов `0x00002000-0x00002FFF`. В отличие от предыдущих диапазонов адресов, где каждый адрес был закреплен за соответствующей позицией символа в сетке `80x30`, адреса данного диапазона распределены следующим образом: +Для хранения шрифтов в модуле отведён диапазон адресов `0x00002000-0x00002FFF`. В отличие от предыдущих диапазонов адресов, где каждый адрес был закреплён за соответствующей позицией символа в сетке `80x30`, адреса данного диапазона распределены следующим образом: - 0-ой байт — нулевая (верхняя) строчка символа с кодом 0; - 1-ый байт — первая строчка символа с кодом 0; @@ -705,7 +709,7 @@ _Рисунок 6. Отрисовка символа `F` в разрешении Прототип vga-модуля следующий: -```SystemVerilog +```Verilog module vgachargen ( input logic clk_i, // системный синхроимпульс input logic clk100m_i, // клок с частотой 100МГц @@ -763,7 +767,7 @@ module vgachargen ( Для управления данным модулем, необходимо написать модуль-контроллер со следующим прототипом: -```SystemVerilog +```Verilog module vga_sb_ctrl ( input logic clk_i, input logic rst_i, @@ -814,20 +818,20 @@ module vga_sb_ctrl ( Все эти сигналы мультиплексируются / демультиплексируются с помощью одного и того же управляющего сигнала: `addr_i[13:12]` в соответствии с диапазонами адресов (рис. 4): - `addr_i[13:12] == 2'b00` - - `req_i` подается на вход `char_map_req_i`, + - `req_i` подаётся на вход `char_map_req_i`, - `write_enable_i` поступает на вход `char_map_we_i`, - - `char_map_rdata_o` подается на выход `read_data_o`; + - `char_map_rdata_o` подаётся на выход `read_data_o`; - `addr_i[13:12] == 2'b01` - `req_i` поступает на вход `col_map_req_i`, - `write_enable_i` поступает на вход `col_map_we_i`, - - `col_map_rdata_o` подается на выход `read_data_o`; + - `col_map_rdata_o` подаётся на выход `read_data_o`; - `addr_i[13:12] == 2'b10` - `req_i` поступает на вход `char_tiff_req_i`, - `write_enable_i` поступает на вход `char_tiff_we_i`, - - `char_tiff_rdata_o` подается на выход `read_data_o`. + - `char_tiff_rdata_o` подаётся на выход `read_data_o`. > [!Important] -> Обратите внимание на то, что контроллер vga является единственным контроллером, для которого не нужно реализовывать регистр перед выходом read_data_o для реализации синхронного чтения. Данная особенность обусловлена тем, что внутри модуля `vgachargen` уже находится блочная память с синхронным портом на чтение. Добавление еще одного регистра приведет к тому, данные будут "опаздывать" на один такт. Таким образом, данные на выход `read_data_o` необходимо подавать с помощью чисто комбинационной логики. +> Обратите внимание на то, что контроллер vga является единственным контроллером, для которого не нужно реализовывать регистр перед выходом read_data_o для реализации синхронного чтения. Данная особенность обусловлена тем, что внутри модуля `vgachargen` уже находится блочная память с синхронным портом на чтение. Добавление ещё одного регистра приведёт к тому, данные будут "опаздывать" на один такт. Таким образом, данные на выход `read_data_o` необходимо подавать с помощью чисто комбинационной логики. ## Список использованной литературы diff --git a/Labs/13. Peripheral units/lab_13.tb_system.sv b/Labs/13. Peripheral units/lab_13.tb_processor_system.sv similarity index 98% rename from Labs/13. Peripheral units/lab_13.tb_system.sv rename to Labs/13. Peripheral units/lab_13.tb_processor_system.sv index 92da3812..dc71f1da 100644 --- a/Labs/13. Peripheral units/lab_13.tb_system.sv +++ b/Labs/13. Peripheral units/lab_13.tb_processor_system.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module lab_13_tb_system(); +module lab_13_tb_processor_system(); import peripheral_pkg::*; diff --git a/Labs/14. Programming/README.md b/Labs/14. Programming/README.md index 92e50f6d..0bca3f1d 100644 --- a/Labs/14. Programming/README.md +++ b/Labs/14. Programming/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 14 "Высокоуровневое программирование" +# Лабораторная работа №14 "Высокоуровневое программирование" Благодаря абстрагированию можно создавать действительно сложные системы — из вентилей можно собрать модули, из модулей микроархитектуру и так далее. В этом контексте архитектура выступает как фундамент, на котором строится программный стек абстракций. На основе архитектур строятся ассемблеры, на основе которых "строятся" языки высокого уровня, на основе которых создаются фреймворки и метафреймворки, что обеспечивает более высокий уровень и удобство при разработке новых программ. Давайте немного глубже погрузимся в этот стек. @@ -31,7 +31,7 @@ > — Но разве в процессе компиляции исходного кода на языке Си мы не получаем программу, написанную на языке ассемблера? Получится ведь тот же код, что мы могли написать и сами. -Штука в том, что ассемблерный код, который писали ранее вы отличается от ассемблерного кода, генерируемого компилятором. Код, написанный вами, обладал, скажем так... более тонким микро-контролем хода программы. Когда вы писали программу, вы знали какой у вас размер памяти, где в памяти расположены инструкции, а где данные (ну, при написании программ вы почти не пользовались памятью данных, а когда пользовались — просто лупили по случайным адресам и все получалось). Вы пользовались всеми регистрами регистрового файла по своему усмотрению, без ограничений. Однако, представьте на секунду, что вы пишете проект на ассемблере вместе с коллегой: вы пишите одни функции, а он другие. Как в таком случае вы будете пользоваться регистрами регистрового файла? Ведь если вы будете пользоваться одними и теми же регистрами, вызов одной функции может испортить данные в другой. Поделите его напополам и будете пользоваться каждый своей половиной? Но что будет, если к проекту присоединится еще один коллега — придется делить регистровый файл уже на три части? Так от него уже ничего не останется. Для разрешения таких ситуаций было разработано [соглашение о вызовах](#соглашение-о-вызовах) (calling convention). +Штука в том, что ассемблерный код, который писали ранее вы отличается от ассемблерного кода, генерируемого компилятором. Код, написанный вами, обладал, скажем так... более тонким микро-контролем хода программы. Когда вы писали программу, вы знали какой у вас размер памяти, где в памяти расположены инструкции, а где данные (ну, при написании программ вы почти не пользовались памятью данных, а когда пользовались — просто лупили по случайным адресам и все получалось). Вы пользовались всеми регистрами регистрового файла по своему усмотрению, без ограничений. Однако, представьте на секунду, что вы пишете проект на ассемблере вместе с коллегой: вы пишите одни функции, а он другие. Как в таком случае вы будете пользоваться регистрами регистрового файла? Ведь если вы будете пользоваться одними и теми же регистрами, вызов одной функции может испортить данные в другой. Поделите его напополам и будете пользоваться каждый своей половиной? Но что будет, если к проекту присоединится ещё один коллега — придётся делить регистровый файл уже на три части? Так от него уже ничего не останется. Для разрешения таких ситуаций было разработано [соглашение о вызовах](#соглашение-о-вызовах) (calling convention). Таким образом, генерируя ассемблерный код, компилятор не может так же, как это делали вы, использовать все ресурсы без каких-либо ограничений — он должен следовать ограничениям, накладываемым на него соглашением о вызовах, а также ограничениям, связанным с тем, что он ничего не знает о памяти устройства, в котором будет исполняться программа — а потому он не может работать с памятью абы как. Работая с памятью, компилятор следует некоторым правилам, благодаря которым после компиляции компоновщик сможет собрать программу под ваше устройство с помощью специального скрипта. @@ -43,11 +43,11 @@ При работе с оберегаемыми регистрами, функция должна гарантировать, что перед возвратом в этих регистрах останется тоже самое значение, что было при вызове функции. То есть, если функция собирается записать что-то в оберегаемый регистр, она должна сохранить перед этим его значение на стек, а затем, перед возвратом, вернуть это значение со стека обратно в этот же регистр. -Простая аналогия — в маленькой квартире двое делят один рабочий стол по времени. Каждый использует стол по полной, но после себя он должен оставить половину стола соседа (оберегаемые регистры) в том же виде, в котором ее получил, а со своей (необерегаемые регистры) делает что хочет. Кстати, вещи соседа, чтоб не потерять, убирают на стопку (**stack**) рядом (в основную память). +Простая аналогия — в маленькой квартире двое делят один рабочий стол по времени. Каждый использует стол по полной, но после себя он должен оставить половину стола соседа (оберегаемые регистры) в том же виде, в котором её получил, а со своей (необерегаемые регистры) делает что хочет. Кстати, вещи соседа, чтоб не потерять, убирают на стопку (**stack**) рядом (в основную память). С необерегаемыми регистрами функция может работать как ей угодно — не существует никаких гарантий, которые вызванная функция должна исполнить. При этом, если функция вызывает другую функцию, она точно так же не получает никаких гарантий, что вызванная функция оставит значения необерегаемых регистров без изменений, поэтому если там хранятся значения, которые потребуются по окончанию выполнения вызываемой функции, эти значения необходимо сохранить на стек. -В таблице ниже приведено разделение регистров на оберегаемые (в правом столбце записано `Callee`, т.е. за их сохранение отвечает вызванная функция) и необерегаемые (`Caller` — за сохранение отвечает вызывающая функция). Кроме того, есть три регистра, для которых правый столбец не имеет значения: нулевой регистр (поскольку его невозможно изменить) и указатели на поток и глобальную область памяти. По соглашению о вызовах, эти регистры нельзя использовать для вычислений функций, они изменяются только по заранее оговоренным ситуациям. +В _таблице 1_ приведено разделение регистров на оберегаемые (в правом столбце записано `Callee`, т.е. за их сохранение отвечает вызванная функция) и необерегаемые (`Caller` — за сохранение отвечает вызывающая функция). Кроме того, есть три регистра, для которых правый столбец не имеет значения: нулевой регистр (поскольку его невозможно изменить) и указатели на поток и глобальную область памяти. По соглашению о вызовах, эти регистры нельзя использовать для вычислений функций, они изменяются только по заранее оговорённым ситуациям. В столбце `ABI name` записывается синоним имени регистра, связанный с его функциональным назначением (см. описание регистра). Часто ассемблеры одинаково воспринимают обе формы написания имени регистров. @@ -69,9 +69,9 @@ _Таблица 1. Ассемблерные мнемоники для целочисленных регистров RISC-V и их назначение в соглашении о вызовах[1, стр. 6]._ -Несмотря на то, что указатель на стек помечен как Callee-saved регистр, это не означает, что вызываемая функция может записать в него что заблагорассудится, предварительно сохранив его значение на стек. Ведь как вы вернете значение указателя на стек со стека, если в регистре указателя на стек лежит что-то не то? +Несмотря на то, что указатель на стек помечен как Callee-saved регистр, это не означает, что вызываемая функция может записать в него что заблагорассудится, предварительно сохранив его значение на стек. Ведь как вы вернёте значение указателя на стек со стека, если в регистре указателя на стек лежит что-то не то? -Запись `Callee` означает, что к моменту возврата из вызываемой функции, значение Callee-saved регистров должно быть ровно таким же, каким было в момент вызова функций. Для s0-s11 регистров это осуществляется путем сохранения их значений на стек. При этом, перед каждым сохранением на стек, изменяется значение указателя на стек таким образом, чтобы он указывал на сохраняемое значение (обычно он декрементируется). Затем, перед возвратом из функции все сохраненные на стек значения восстанавливаются, попутно изменяя значение указателя на стек противоположным образом (инкрементируют его). Таким образом, несмотря на то, что значение указателя на стек менялось в процессе работы вызываемой функции, к моменту выхода из нее, его значение в итоге останется тем же. +Запись `Callee` означает, что к моменту возврата из вызываемой функции, значение Callee-saved регистров должно быть ровно таким же, каким было в момент вызова функций. Для s0-s11 регистров это осуществляется путём сохранения их значений на стек. При этом, перед каждым сохранением на стек, изменяется значение указателя на стек таким образом, чтобы он указывал на сохраняемое значение (обычно он декрементируется). Затем, перед возвратом из функции все сохранённые на стек значения восстанавливаются, попутно изменяя значение указателя на стек противоположным образом (инкрементируют его). Таким образом, несмотря на то что значение указателя на стек менялось в процессе работы вызываемой функции, к моменту выхода из неё, его значение в итоге останется тем же. ### Скрипт для компоновки (linker_script.ld) @@ -79,15 +79,15 @@ _Таблица 1. Ассемблерные мнемоники для целоч В самом простом виде скрипт компоновки состоит из одного раздела: раздела секций, в котором вы и описываете какие части программы куда и в каком порядке необходимо разместить. -Для удобства этого описания существует вспомогательная переменная: счетчик адресов. Этот счетчик показывает в какое место в памяти будет размещена очередная секция (если при размещении секции в явном виде не будет указано иного). На момент начала исполнения скрипта этот счетчик равен нулю. Размещая очередную секцию, счетчик увеличивается на размер размещаемой секции. Допустим, у нас есть два файла `startup.o` и `main.o`, в каждом из которых есть секции `.text` и `.data`. Мы хотим разместить их в памяти следующим образом: сперва разместить секции `.text` обоих файлов, а затем секции `.data`. +Для удобства этого описания существует вспомогательная переменная: счётчик адресов. Этот счётчик показывает в какое место в памяти будет размещена очередная секция (если при размещении секции в явном виде не будет указано иного). На момент начала исполнения скрипта этот счётчик равен нулю. Размещая очередную секцию, счётчик увеличивается на размер размещаемой секции. Допустим, у нас есть два файла `startup.o` и `main.o`, в каждом из которых есть секции `.text` и `.data`. Мы хотим разместить их в памяти следующим образом: сперва разместить секции `.text` обоих файлов, а затем секции `.data`. -В итоге начиная с нулевого адреса будет размещена секция `.text` файла `startup.o`. Она будет размещена именно там, поскольку счетчик адресов в начале скрипта равен нулю, а очередная секция размещается по адресу, куда указывает счетчик адресов. После этого, счетчик будет увеличен на размер этой секции и секция `.text` файла `main.o` будет размещена сразу же за секцией `.text` файла `startup.o`. После этого счетчик адресов будет увеличен на размер этой секции. То же самое произойдет и при размещении оставшихся секций. +В итоге начиная с нулевого адреса будет размещена секция `.text` файла `startup.o`. Она будет размещена именно там, поскольку счётчик адресов в начале скрипта равен нулю, а очередная секция размещается по адресу, куда указывает счётчик адресов. После этого, счётчик будет увеличен на размер этой секции и секция `.text` файла `main.o` будет размещена сразу же за секцией `.text` файла `startup.o`. После этого счётчик адресов будет увеличен на размер этой секции. То же самое произойдёт и при размещении оставшихся секций. Кроме того, вы в любой момент можете изменить значение счетчика адресов. Например, если адресное пространство памяти поделено на две части: под инструкции отводится 512 байт, а под данные 1024 байта. Таким образом, выделенный диапазон адресов для инструкций: `[0:511]`, а для данных: `[512:1535]`. Предположим при этом, что общий объем секций `.text` составляет 416 байт. В этом случае, вы можете сперва разместить секции `.text` так же, как было описано в предыдущем примере, а затем, выставив значение на счетчике адресов равное `512`, описать размещение секций данных. Тогда, между секциями появится разрыв в 96 байт, а данные окажутся в выделенном для них диапазоне адресов. В нашей процессорной системе гарвардская архитектура. Это значит, что память инструкций и данных у нас независимы друг от друга. Это физически разные устройства, с разными шинами и разным адресным пространством. Однако обе эти памяти имеют общие значения младших адресов: самый младший имеет адрес ноль, следующий адрес 1 и т.д. Таким образом, происходит наложение адресных пространств памяти инструкций и памяти данных. Компоновщику трудно работать в таких условиях: "как я записать что по этому адресу будет размещаться секция данных, когда здесь уже размещена секция инструкций". -Есть два механизма для решения этого вопроса. Первый: компоновать секции инструкций и данных по отдельности. В этом случае будет два отдельных скрипта компоновщика. Однако, компоновка секций инструкций зависит от компоновки секций данных (в частности, от того по каким адресам будут размещены стек и .bss-секция, а также указатель на глобальную область данных), поскольку в часть инструкций необходимо прописать конкретные адреса. В этом случае, придется делать промежуточные операции в виде экспорта глобальных символов в отдельный объектный файл, который будет использован при компоновке секции инструкций, что кажется некоторым переусложнением. +Есть два механизма для решения этого вопроса. Первый: компоновать секции инструкций и данных по отдельности. В этом случае будет два отдельных скрипта компоновщика. Однако, компоновка секций инструкций зависит от компоновки секций данных (в частности, от того по каким адресам будут размещены стек и .bss-секция, а также указатель на глобальную область данных), поскольку в часть инструкций необходимо прописать конкретные адреса. В этом случае, придётся делать промежуточные операции в виде экспорта глобальных символов в отдельный объектный файл, который будет использован при компоновке секции инструкций, что кажется некоторым переусложнением. Вместо этого, будет использован другой подход, механизм виртуальных адресов (**Virtual Memory Address**, **VMA**) и адресов загрузки (**Load Memory Address**, **LMA**). @@ -96,7 +96,7 @@ _Таблица 1. Ассемблерные мнемоники для целоч Обычно LMA совпадает с VMA. Однако в некоторых случаях они могут быть и различны (например, изначально секция данных записывается в ROM, а перед выполнением программы, копируется из ROM в RAM). В этом случае, LMA — это адрес секции в ROM, а VMA — адрес секции в RAM. -Таким образом, мы можем сделать общие VMA (процессор, обращаясь к секциям инструкций и данных будет использовать пересекающееся адресное пространство), а конфликт размещения секций компоновщиком разрешить задав какой-нибудь заведомо большой VMA для секции данных. В последствии, мы просто проигнорируем этот адрес, проинициализировав память данных начиная с нуля. +Таким образом, мы можем сделать общие VMA (процессор, обращаясь к секциям инструкций и данных будет использовать пересекающееся адресное пространство), а конфликт размещения секций компоновщиком разрешить, задав какой-нибудь заведомо большой VMA для секции данных. В последствии, мы просто проигнорируем этот адрес, проинициализировав память данных начиная с нуля. Помимо прочего, в скрипте компоновщика необходимо прописать, каков [порядок следования байт](https://en.wikipedia.org/wiki/Endianness), где будет находиться стек, и какое будет значение у указателя на глобальную область памяти. @@ -112,7 +112,7 @@ ENTRY(_start) /* мы сообщаем компоно /* В данном разделе указывается структура памяти: - Сперва идет регион "instr_mem", являющийся памятью с исполняемым кодом + Сперва идёт регион "instr_mem", являющийся памятью с исполняемым кодом (об этом говорит аттрибут 'x'). Этот регион начинается с адреса 0x00000000 и занимает 1024 байта. Далее идет регион "data_mem", начинающийся с адреса 0x00000000 и занимающий @@ -150,12 +150,12 @@ SECTIONS /* В скриптах компоновщика есть внутренняя переменная, записываемая как '.' - Эта переменная называется "счетчиком адресов". Она хранит текущий адрес в + Эта переменная называется "счётчиком адресов". Она хранит текущий адрес в памяти. - В начале файла она инициализируется нулем. Добавляя новые секции, эта + В начале файла она инициализируется нулём. Добавляя новые секции, эта переменная будет увеличиваться на размер каждой новой секции. Если при размещении секций не указывается никакой адрес, они будут размещены - по текущему значению счетчика адресов. + по текущему значению счётчика адресов. Этой переменной можно присваивать значения, после этого, она будет увеличиваться с этого значения. Подробнее: @@ -164,7 +164,7 @@ SECTIONS /* Следующая команда сообщает, что начиная с адреса, которому в данных момент - равен счетчик адресов (в данный момент, начиная с нуля) будет находиться + равен счётчик адресов (в данный момент, начиная с нуля) будет находиться секция .text итогового файла, которая состоит из секций .boot, а также всех секций, начинающихся на .text во всех переданных компоновщику двоичных файлах. @@ -194,8 +194,8 @@ SECTIONS */ .data : AT (0x00800000) { /* - Общепринято присваивать GP значение равное началу секции данных, смещенное - на 2048 байт вперед. + Общепринято присваивать GP значение равное началу секции данных, смещённое + на 2048 байт вперёд. Благодаря относительной адресации со смещением в 12 бит, можно адресоваться на начало секции данных, а также по всему адресному пространству вплоть до 4096 байт от начала секции данных, что сокращает объем требуемых для @@ -212,7 +212,7 @@ SECTIONS /* Поскольку мы не знаем суммарный размер всех используемых секций данных, - перед размещением других секций, необходимо выровнять счетчик адресов по + перед размещением других секций, необходимо выровнять счётчик адресов по 4х-байтной границе. */ . = ALIGN(4); @@ -222,21 +222,21 @@ SECTIONS BSS (block started by symbol, неофициально его расшифровывают как better save space) — это сегмент, в котором размещаются неинициализированные статические переменные. В стандарте Си сказано, что такие переменные - инициализируются нулем (или NULL для указателей). Когда вы создаете - статический массив — он должен быть размещен в исполняемом файле. + инициализируются нулём (или NULL для указателей). Когда вы создаёте + статический массив — он должен быть размещён в исполняемом файле. Без bss-секции, этот массив должен был бы занимать такой же объем - исполняемого файла, какого объема он сам. Массив на 1000 байт занял бы + исполняемого файла, какого объёма он сам. Массив на 1000 байт занял бы 1000 байт в секции .data. Благодаря секции bss, начальные значения массива не задаются, вместо этого здесь только записываются названия переменных и их адреса. Однако на этапе загрузки исполняемого файла теперь необходимо принудительно занулить участок памяти, занимаемый bss-секцией, поскольку статические - переменные должны быть проинициализированы нулем. + переменные должны быть проинициализированы нулём. Таким образом, bss-секция значительным образом сокращает объем исполняемого файла (в случае использования неинициализированных статических массивов) ценой увеличения времени загрузки этого файла. Для того, чтобы занулить bss-секцию, в скрипте заводятся две переменные, - указывающие на начало и конец bss-секции посредством счетчика адресов. + указывающие на начало и конец bss-секции посредством счётчика адресов. Подробнее: https://en.wikipedia.org/wiki/.bss @@ -254,7 +254,7 @@ SECTIONS /*================================= Секция аллоцированных данных завершена, остаток свободной памяти отводится под программный стек, стек прерываний и (возможно) кучу. В соглашении о - вызовах архитектуры RISC-V сказано, что стек растет снизу вверх, поэтому + вызовах архитектуры RISC-V сказано, что стек растёт снизу вверх, поэтому наша цель разместить его в самых последних адресах памяти. Поскольку стеков у нас два, в самом низу мы разместим стек прерываний, а над ним программный стек. При этом надо обеспечить защиту программного @@ -267,7 +267,7 @@ SECTIONS ASSERT(. < (LENGTH(data_mem) - _trap_stack_size - _stack_size), "Program size is too big") - /* Перемещаем счетчик адресов над стеком прерываний (чтобы после мы могли + /* Перемещаем счётчик адресов над стеком прерываний (чтобы после мы могли использовать его в вызове ALIGN) */ . = LENGTH(data_mem) - _trap_stack_size; @@ -283,24 +283,25 @@ SECTIONS ASSERT(_stack_ptr <= LENGTH(data_mem) - _trap_stack_size, "SP exceed memory size") - /* Перемещаем счетчик адресов в конец памяти (чтобы после мы могли + /* Перемещаем счётчик адресов в конец памяти (чтобы после мы могли использовать его в вызове ALIGN) */ . = LENGTH(data_mem); /* Обычно память имеет размер, кратный 16, но на случай, если это не так, мы - делаем проверку, после которой мы либо остаемся в самом конце памяти (если + делаем проверку, после которой мы либо остаёмся в самом конце памяти (если конец кратен 16), либо поднимаемся на 16 байт вверх от края памяти, - округленного до 16 в сторону большего значения + округлённого до 16 в сторону большего значения */ _trap_stack_ptr = ALIGN(16) <= LENGTH(data_mem) ? ALIGN(16) : ALIGN(16) - 16; ASSERT(_trap_stack_ptr <= LENGTH(data_mem), "ISP exceed memory size") } + ``` _Листинг 1. Пример скрипта компоновщика с комментариями._ -Обратите внимание на указанные размеры памяти инструкций и данных. Они отличаются от размеров, которые использовались ранее в пакете `memory_pkg`. Дело в том, что пока система и исполняемые ей программы были простыми, в большом объеме памяти не было нужды и меньший размер значительно сокращал время синтеза системы. Однако в данный момент, чтобы обеспечить программе достаточно места под инструкции, а так же программный стек и стек прерываний, необходимо увеличить объемы памяти инструкций и памяти данных. Для этого необходимо обновить значения параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` на 32'd1024 и 32'd2048 соответственно. В зависимости от сложности вашего проекта, в будущем вам может снова потребоваться изменять размер памяти в вашей системе. Помните, все изменения в memory_pkg должны отражаться и в скрипте компоновщика для вашей системы. +Обратите внимание на указанные размеры памяти инструкций и данных. Они отличаются от размеров, которые использовались ранее в пакете `memory_pkg`. Дело в том, что пока система и исполняемые ей программы были простыми, в большом объеме памяти не было нужды и меньший размер значительно сокращал время синтеза системы. Однако в данный момент, чтобы обеспечить программе достаточно места под инструкции, а также программный стек и стек прерываний, необходимо увеличить объемы памяти инструкций и памяти данных. Для этого необходимо обновить значения параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` на 32'd1024 и 32'd2048 соответственно. В зависимости от сложности вашего проекта, в будущем вам может снова потребоваться изменять размер памяти в вашей системе. Помните, все изменения в memory_pkg должны отражаться и в скрипте компоновщика для вашей системы. ### Файл первичных команд при загрузке (startup.S) @@ -360,7 +361,7 @@ _endless_loop: # https://github.com/twlostow/urv-core/blob/master/sw/common/irq.S # Из реализации убраны сохранения нереализованных CS-регистров. Кроме того, # судя по документу приведенному ниже, обычное ABI подразумевает такое же -# сохранение контекста, что и при программном вызове (EABI подразумевает еще +# сохранение контекста, что и при программном вызове (EABI подразумевает ещё # меньшее сохранение контекста), поэтому нет нужды сохранять весь регистровый # файл. # Документ: @@ -404,7 +405,7 @@ _int_handler: sw t6,68(sp) # Кроме того, мы сохраняем состояние регистров прерываний на случай, если - # произойдет еще одно прерывание. + # произойдет ещё одно прерывание. csrr t0,mscratch csrr t1,mepc csrr a0,mcause @@ -420,7 +421,7 @@ _int_handler: # Восстановление контекста. В первую очередь мы хотим восстановить CS-регистры, # на случай, если происходило вложенное прерывание. Для этого, мы должны # вернуть исходное значение указателя стека прерываний. Однако его нынешнее - # значение нам еще необходимо для восстановления контекста, поэтому мы + # значение нам ещё необходимо для восстановления контекста, поэтому мы # сохраним его в регистр a0, и будем восстанавливаться из него. mv a0,sp @@ -494,7 +495,7 @@ _Листинг 2. Пример содержимого файла первичн Исполняемый файл компилятора тот же самый, флаги компоновки будут следующие: -- `-march=rv32i_zicsr -mabi=ilp32` — те же самые флаги, что были при компиляции (нам все еще нужно указывать архитектуру, иначе компоновщик может скомпоновать объектные файлы со стандартными библиотеками от другой архитектуры) +- `-march=rv32i_zicsr -mabi=ilp32` — те же самые флаги, что были при компиляции (нам все ещё нужно указывать архитектуру, иначе компоновщик может скомпоновать объектные файлы со стандартными библиотеками от другой архитектуры) - `-Wl,--gc-sections` — указать компоновщику удалять неиспользуемые секции (сокращает объем итогового файла) - `-nostartfiles` — указать компоновщику не использовать стартап-файлы стандартных библиотек (сокращает объем файла и устраняет ошибки компиляции из-за конфликтов с используемым стартап-файлом). - `-T linker_script.ld` — передать компоновщику скрипт компоновки @@ -550,13 +551,13 @@ FF5FF06F 04400293 FFF00313 30529073 ... ``` -Обратите внимание что байты не просто склеились в четверки, изменился так же и порядок следования байт. Это важно, т.к. в память данные должны лечь именно в таком (обновленном) порядке байт (см. первую строчку скрипта компоновщика). Когда-то `objcopy` содержал [баг](https://sourceware.org/bugzilla/show_bug.cgi?id=25202), из-за которого порядок следования байт не менялся. В каких-то версиях тулчейна (отличных от представленного в данной лабораторной работе) вы все еще можете столкнуться с подобным поведением. +Обратите внимание что байты не просто склеились в четверки, изменился так же и порядок следования байт. Это важно, т.к. в память данные должны лечь именно в таком (обновленном) порядке байт (см. первую строчку скрипта компоновщика). Когда-то `objcopy` содержал [баг](https://sourceware.org/bugzilla/show_bug.cgi?id=25202), из-за которого порядок следования байт не менялся. В каких-то версиях тулчейна (отличных от представленного в данной лабораторной работе) вы все ещё можете столкнуться с подобным поведением. Вернемся к первой строке: `@00000000`. Как уже говорилось, число, начинающееся с символа `@` говорит САПР, что с этого момента инициализация идет начиная с ячейки памяти, номер которой совпадает с этим числом. Когда вы будете экспортировать секции данных, первой строкой будет: `@20000000`. Так произойдет, поскольку в скрипте компоновщика сказано, указано инициализировать память данных с `0x80000000` адреса (значение которого было поделено на 4, чтобы получить номер 32-битной ячейки памяти). Это было сделано, чтобы не произошло наложения адресов памяти инструкций и памяти данных (см раздел [скрипт для компоновки](#скрипт-для-компоновки-linker_scriptld)). **Чтобы система работала корректно, эту строчку необходимо удалить.** ### Дизассемблирование -В процессе отладки лабораторной работы потребуется много раз смотреть на программный счетчик и текущую инструкцию. Довольно тяжело декодировать инструкцию самостоятельно, чтобы понять, что сейчас выполняется. Для облегчения задачи можно дизасемблировать скомпилированный файл. Полученный файл на языке ассемблера будет хранить адреса инструкций, а также их двоичное и ассемблерное представление. +В процессе отладки лабораторной работы потребуется много раз смотреть на программный счётчик и текущую инструкцию. Довольно тяжело декодировать инструкцию самостоятельно, чтобы понять, что сейчас выполняется. Для облегчения задачи можно дизасемблировать скомпилированный файл. Полученный файл на языке ассемблера будет хранить адреса инструкций, а также их двоичное и ассемблерное представление. Пример дизасемблированного файла: @@ -730,8 +731,8 @@ _Листинг 4. Пример кода на C++, взаимодействую 2. Разберите принцип взаимодействия с контрольными и статусными регистрами периферийного устройства на примере _Листинга 4_. 3. Обновите значения параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` в пакете `memory_pkg` на 32'd1024 и 32'd2048 соответственно. Поскольку пакеты не являются модулями, вы не увидите их во вкладке `Hierarchy` окна исходников, вместо этого вы сможете найти их во вкладках `Libraries` и `Compile order`. 4. Напишите программу для своего индивидуального задания и набора периферийных устройств на языке C или C++. В случае написания кода на C++ помните о необходимости добавления `extern "C"` перед определением функции `int_handler`. - 1. В описываемой программе обязательно должны присутствовать функции `main` и `int_handler`, т.к. в стартап-файле описаны вызовы этих функций. При необходимости, вы можете описать необходимые вам вспомогательные функции — ограничений на то что должно быть ровне две этих функции нет. - 2. Функция `main` может быть пустой — по ее завершению в стартап-файле предусмотрен бесконечный цикл, из которого процессор сможет выходить только по прерыванию. + 1. В описываемой программе обязательно должны присутствовать функции `main` и `int_handler`, т.к. в стартап-файле описаны вызовы этих функций. При необходимости, вы можете описать необходимые вам вспомогательные функции — ограничений на то, что должно быть ровне две этих функции нет. + 2. Функция `main` может быть пустой — по её завершению в стартап-файле предусмотрен бесконечный цикл, из которого процессор сможет выходить только по прерыванию. 3. В функции `int_handler` вам необходимо считать поступившие от устройства ввода входные данные. 4. Вам необходимо самостоятельно решить, как вы хотите построить ход работы вашей программы: будет ли ваше индивидуальное задание вычисляться всего лишь 1 раз в функции `main`, данные в которую поступят от функции `int_handler` через глобальные переменные, или же оно будет постоянно пересчитываться при каждом вызове `int_handler`. 5. Доступ к регистрам контроля и статуса необходимо осуществлять посредством указателей на структуры, объявленные в файле [platform.h](./platform.h). В случае VGA-контроллера, доступ к областям памяти осуществляется через экземпляр структуры (а не указатель на нее), содержащий имена массивов `char_map`, `color_map` и `tiff_map`. @@ -746,4 +747,12 @@ _Листинг 4. Пример кода на C++, взаимодействую 2. Для отладки во время моделирования будет удобно использовать дизасемблерный файл, ориентируясь на сигналы адреса и данных шины инструкций. 10. Проверьте корректное исполнение программы процессором в ПЛИС. ---- +## Список источников: + +1. [ISC-V ABIs Specification, Document Version 1.0', Editors Kito Cheng and Jessica +Clarke, RISC-V International, November 2022](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/releases/download/v1.0/riscv-abi.pdf); +2. [Using LD, the GNU linker — Linker Scripts](https://home.cs.colorado.edu/~main/cs1300/doc/gnu/ld_3.html#IDX338); +3. [Google Gropus — "gcc gp (global pointer) register"](https://groups.google.com/a/groups.riscv.org/g/sw-dev/c/60IdaZj27dY/m/s1eJMlrUAQAJ?pli=1); +4. [Wikipedia — .bss](https://en.wikipedia.org/wiki/.bss). + + diff --git a/Labs/15. Programming device/README.md b/Labs/15. Programming device/README.md index fcb854e6..4c3da0e4 100644 --- a/Labs/15. Programming device/README.md +++ b/Labs/15. Programming device/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 15 "Программатор" +# Лабораторная работа №15 "Программатор" Чтобы выпустить микроконтроллер в "дикую природу", то есть, чтобы его можно было использовать не в лабораторных условиях, а независимо от всего этого дополнительного оборудования, необходимо предусмотреть механизм замены исполняемой программы. @@ -54,7 +54,7 @@ Опишем данный автомат в виде графа переходов: -![https://upload.wikimedia.org/wikipedia/commons/9/9e/Turnstile_state_machine_colored.svg](https://upload.wikimedia.org/wikipedia/commons/9/9e/Turnstile_state_machine_colored.svg) +![../../.pic/Labs/lab_15_programming_device/fig_01.svg](../../.pic/Labs/lab_15_programming_device/fig_01.svg) _Рисунок 1. Граф переходов конечного автомата для турникета[[1]](https://en.wikipedia.org/wiki/Finite-state_machine)._ @@ -62,17 +62,17 @@ _Рисунок 1. Граф переходов конечного автомат Как мы видим, повторное опускание жетона в разблокированном состоянии приводит к сохранению этого состояния (но турникет не запоминает, что было опущено 2 жетона, и после первого же прохода станет заблокирован). В случае попытки поворота треноги в заблокированном состоянии, автомат так и останется в заблокированном состоянии. -Так же необходимо оговорить приоритет переходов: в первую очередь проверяется попытка поворота треноги, в случае если такой попытки не было, проверяется опускание монетки. Такой приоритет можно было бы указать и на графе, показав на ребрах что переход в состояние unlocked возможен только при отсутствии сигнала `push`. +Так же необходимо оговорить приоритет переходов: в первую очередь проверяется попытка поворота треноги, в случае если такой попытки не было, проверяется опускание монетки. Такой приоритет можно было бы указать и на графе, показав на рёбрах что переход в состояние unlocked возможен только при отсутствии сигнала `push`. ### Реализация конечных автоматов в SystemVerilog -Глядя на описание составляющих конечного автомата, вы могли задаться вопросом: чем автомат отличается от последовательностной логики, ведь она состоит из тех же компонент. Ответом будет: ничем. Конечные автоматы являются математической абстракцией над функцией последовательностной логики[[3]](https://www.allaboutcircuits.com/textbook/digital/chpt-11/finite-state-machines/). Иными словами — конечный автомат, это просто другой способ представления последовательностной логики, а значит вы уже умеете их реализовывать. +Глядя на описание составляющих конечного автомата, вы могли задаться вопросом: чем автомат отличается от последовательностной логики, ведь она состоит из тех же компонент. Ответом будет: ничем. Конечные автоматы являются математической абстракцией над функцией последовательностной логики[[2]](https://www.allaboutcircuits.com/textbook/digital/chpt-11/finite-state-machines/). Иными словами — конечный автомат, это просто другой способ представления последовательностной логики, а значит вы уже умеете их реализовывать. Для реализации регистра состояния конечного автомата будет удобно воспользоваться специальным типом языка **SystemVerilog**, который называется `enum` (**перечисление**). Перечисления позволяют объявить объединенный набор именованных констант. В дальнейшем, объявленные имена можно использовать вместо перечисленных значений, им соответствующих, что повышает читаемость кода. Если не указано иного, первому имени присваивается значение `0`, каждое последующее увеличивается на `1` относительно предыдущего значения. -```SystemVerilog +```Verilog module turnstile_fsm( input logic clk, input logic rst, @@ -81,26 +81,27 @@ module turnstile_fsm( output logic is_locked ) - enum logic {LOCKED=1, UNLOCKED=0} state; + enum logic {LOCKED=1, UNLOCKED=0} state; - assign is_locked = state == LOCKED; + assign is_locked = state == LOCKED; - always_ff @(posedge clk) begin - if(rst) begin + always_ff @(posedge clk) begin + if(rst) begin + state <= LOCKED; + end + else begin + if(push) begin state <= LOCKED; end + else if (coin) begin + state <= UNLOCKED; + end else begin - if(push) begin - state <= LOCKED; - end - else if (coin) begin - state <= UNLOCKED; - end - else begin - state <= state; - end + state <= state; end end + end +endmodule ``` _Листинг 1. Пример реализации конечного автомата для турникета._ @@ -111,9 +112,9 @@ _Листинг 1. Пример реализации конечного авто _Рисунок 2. Вывод значений объекта `enum` на временную диаграмму._ -Для описания регистра состояния часто используют отдельный комбинационный сигнал, который подается непосредственно на его вход (часто именуемый как `next_state`). Приведенный выше автомат турникета слишком простой, чтобы показать преимущества такого подхода. Предположим, что в момент перехода из состояния `locked` в состояние `unlocked` мы хотим, чтобы загоралась и сразу гасла зеленая лампочка. Без сигнала `next_state` подобный модуль можно было бы описать как: +Для описания регистра состояния часто используют отдельный комбинационный сигнал, который подаётся непосредственно на его вход (часто именуемый как `next_state`). Приведённый выше автомат турникета слишком простой, чтобы показать преимущества такого подхода. Предположим, что в момент перехода из состояния `locked` в состояние `unlocked` мы хотим, чтобы загоралась и сразу гасла зелёная лампочка. Без сигнала `next_state` подобный модуль можно было бы описать как: -```SystemVerilog +```Verilog module turnstile_fsm( input logic clk, input logic rst, @@ -123,36 +124,37 @@ module turnstile_fsm( output logic green_light ) - enum logic {LOCKED=1, UNLOCKED=0} state; + enum logic {LOCKED=1, UNLOCKED=0} state; - assign is_locked = ststate == LOCKED; + assign is_locked = ststate == LOCKED; - // (!push) && coin — условие перехода в состояние UNLOCKED - assign green_light = (state == LOCKED) && (!push) && coin; + // (!push) && coin — условие перехода в состояние UNLOCKED + assign green_light = (state == LOCKED) && (!push) && coin; - always_ff @(posedge clk) begin - if(rst) begin + always_ff @(posedge clk) begin + if(rst) begin + state <= LOCKED; + end + else begin + if(push) begin state <= LOCKED; end + else if (coin) begin + state <= UNLOCKED; + end else begin - if(push) begin - state <= LOCKED; - end - else if (coin) begin - state <= UNLOCKED; - end - else begin - state <= state; - end + state <= state; end end + end +endmodule ``` -_Листинг 2. Пример реализации конечного автомата для усложненного турникета._ +_Листинг 2. Пример реализации конечного автомата для усложнённого турникета._ Используя сигнал `next_state`, автомат мог бы быть переписан следующим образом: -```SystemVerilog +```Verilog module turnstile_fsm( input logic clk, input logic rst, @@ -162,37 +164,38 @@ module turnstile_fsm( output logic green_light ) - enum logic {LOCKED=1, UNLOCKED=0} state, next_state; + enum logic {LOCKED=1, UNLOCKED=0} state, next_state; - assign is_locked = state; + assign is_locked = state; - assign green_light = (state == LOCKED) && (next_state == UNLOCKED); + assign green_light = (state == LOCKED) && (next_state == UNLOCKED); - always_ff @(posedge clk) begin - if(rst) begin - state <= LOCKED; - end - else begin - state <= next_state - end + always_ff @(posedge clk) begin + if(rst) begin + state <= LOCKED; + end + else begin + state <= next_state end + end - always_comb begin - if(push) begin - next_state = LOCKED; - end - else if (coin) begin - next_state = UNLOCKED; - end - else begin - next_state = state; - end + always_comb begin + if(push) begin + next_state = LOCKED; + end + else if (coin) begin + next_state = UNLOCKED; + end + else begin + next_state = state; end + end +endmodule ``` -_Листинг 3. Пример реализации конечного автомата для усложненного турникета с использованием сигнала next\_state._ +_Листинг 3. Пример реализации конечного автомата для усложнённого турникета с использованием сигнала next\_state._ -На первый взгляд может показаться, что так даже сложнее. Во-первых, появился дополнительный сигнал. Во-вторых, появился еще один `always`-блок. Однако представьте на секунду, что условиями перехода будут не однобитные входные сигналы, а какие-нибудь более сложные условия. И что от них будет зависеть не один выходной сигнал, а множество как выходных сигналов, так и внутренних элементов памяти помимо регистра состояний. В этом случае, сигнал `next_state` позволит избежать дублирования множества условий. +На первый взгляд может показаться, что так даже сложнее. Во-первых, появился дополнительный сигнал. Во-вторых, появился ещё один `always`-блок. Однако представьте на секунду, что условиями перехода будут не однобитные входные сигналы, а какие-нибудь более сложные условия. И что от них будет зависеть не один выходной сигнал, а множество как выходных сигналов, так и внутренних элементов памяти помимо регистра состояний. В этом случае, сигнал `next_state` позволит избежать дублирования множества условий. Важно отметить, что объектам типа `enum` можно присваивать только перечисленные константы и объекты того же типа. Иными словами, `state` можно присваивать значения `LOCKED`/`UNLOCKED` и `next_state`, но нельзя, к примеру, присвоить `1'b0`. @@ -206,9 +209,9 @@ _Листинг 3. Пример реализации конечного авто ### Перезаписываемая память инструкций -Поскольку ранее из памяти инструкций можно было только считывать данные, но не записывать их в нее, программатор не сможет записать принятую из внешнего мира программу. Поэтому необходимо добавить в память инструкций порт на запись. Для того, чтобы различать реализации памяти инструкций, данный модуль будет называться `rw_instr_mem`: +Поскольку ранее из памяти инструкций можно было только считывать данные, но не записывать их в неё, программатор не сможет записать принятую из внешнего мира программу. Поэтому необходимо добавить в память инструкций порт на запись. Для того, чтобы различать реализации памяти инструкций, данный модуль будет называться `rw_instr_mem`: -```SystemVerilog +```Verilog module rw_instr_mem import memory_pkg::INSTR_MEM_SIZE_BYTES; import memory_pkg::INSTR_MEM_SIZE_WORDS; @@ -269,7 +272,7 @@ _Рисунок 3. Граф перехода между состояниями Ниже представлен прототип модуля с частично реализованной логикой: -```SystemVerilog +```Verilog module bluster ( input logic clk_i, @@ -393,7 +396,6 @@ uart_tx tx( .tx_valid_i (tx_valid ) ); - endmodule ``` @@ -441,21 +443,21 @@ _Листинг 5. Готовая часть программатора._ Для работы логики переходов, необходимо реализовать счетчики `size_counter`, `flash_counter`, `msg_counter`. -`size_counter` должен сбрасываться в значение `4`, а также принимать это значение во всех состояниях кроме: `RCV_SIZE`, `RCV_NEXT_COMMAND`. В данных двух состояниях счетчик должен декрементироваться в случае, если `rx_valid` равен единице. +`size_counter` должен сбрасываться в значение `4`, а также принимать это значение во всех состояниях кроме: `RCV_SIZE`, `RCV_NEXT_COMMAND`. В данных двух состояниях счётчик должен декрементироваться в случае, если `rx_valid` равен единице. -`flash_counter` должен сбрасываться в значение `flash_size`, а также принимать это значение во всех состояниях кроме `FLASH`. В этом состоянии счетчик должен декрементироваться в случае, если `rx_valid` равен единице. +`flash_counter` должен сбрасываться в значение `flash_size`, а также принимать это значение во всех состояниях кроме `FLASH`. В этом состоянии счётчик должен декрементироваться в случае, если `rx_valid` равен единице. `msg_counter` должен сбрасываться в значение `INIT_MSG_SIZE-1` (в _Листинге 5_ объявлены параметры `INIT_MSG_SIZE`, `FLASH_MSG_SIZE` и `ACK_MSG_SIZE`). -Счетчик должен инициализироваться следующим образом: +счётчик должен инициализироваться следующим образом: -- в состоянии `FLASH` счетчик должен принимать значение `FLASH_MSG_SIZE-1`, -- в состоянии `RCV_SIZE` счетчик должен принимать значение `ACK_MSG_SIZE-1`, -- в состоянии `RCV_NEXT_COMMAND` счетчик должен принимать значение `INIT_MSG_SIZE-1`. +- в состоянии `FLASH` счётчик должен принимать значение `FLASH_MSG_SIZE-1`, +- в состоянии `RCV_SIZE` счётчик должен принимать значение `ACK_MSG_SIZE-1`, +- в состоянии `RCV_NEXT_COMMAND` счётчик должен принимать значение `INIT_MSG_SIZE-1`. -В состояниях: `INIT_MSG`, `SIZE_ACK`, `FLASH_ACK` счетчик должен декрементироваться в случае, если `tx_valid` равен единице. +В состояниях: `INIT_MSG`, `SIZE_ACK`, `FLASH_ACK` счётчик должен декрементироваться в случае, если `tx_valid` равен единице. -Во всех остальных ситуациях счетчик должен сохранять свое значение. +Во всех остальных ситуациях счётчик должен сохранять свое значение. ##### Реализация сигналов, подключаемых к uart_tx @@ -469,11 +471,11 @@ _Листинг 5. Готовая часть программатора._ Сигнал `tx_data` должен нести очередной байт одного из передаваемых сообщений: -- в состоянии `INIT_MSG` передается очередной байт сообщения `init_msg` -- в состоянии `SIZE_ACK` передается очередной байт сообщения `flash_size` -- в состоянии `FLASH_ACK` передается очередной байт сообщения `flash_msg`. +- в состоянии `INIT_MSG` передаётся очередной байт сообщения `init_msg` +- в состоянии `SIZE_ACK` передаётся очередной байт сообщения `flash_size` +- в состоянии `FLASH_ACK` передаётся очередной байт сообщения `flash_msg`. -В остальных состояниях он равен нулю. Для отсчета байт используется счетчик `msg_counter`. +В остальных состояниях он равен нулю. Для отсчёта байт используется счётчик `msg_counter`. ##### Реализация интерфейсов памяти инструкций и данных @@ -531,7 +533,7 @@ _Рисунок 4. Интеграция программатора в `riscv_uni Необходимо подключить отладочный стенд к последовательному порту компьютера (в случае платы Nexys A7 — достаточно просто подключить плату usb-кабелем, как это делалось на протяжении всех лабораторных для прошивки). Необходимо будет узнать COM-порт, по которому отладочный стенд подключен к компьютеру. Определить нужный COM-порт на операционной системе Windows можно через "Диспетчер устройств", который можно открыть через меню пуск. В данном окне необходимо найти вкладку "Порты (COM и LPT)", раскрыть ее, а затем подключить отладочный стенд через USB-порт (если тот еще не был подключен). В списке появится новое устройство, а в скобках будет указан нужный COM-порт. -Подключив отладочный стенд к последовательному порту компьютера и сконфигурировав ПЛИС вашим проектом остается проинициализировать память. Сделать это можно с помощью предоставленного скрипта, пример запуска которого приведен в листинге 6. +Подключив отладочный стенд к последовательному порту компьютера и сконфигурировав ПЛИС вашим проектом, остается проинициализировать память. Сделать это можно с помощью предоставленного скрипта, пример запуска которого приведен в листинге 6. ```bash # Пример использования скрипта. Сперва указываются опциональные аргументы @@ -563,35 +565,28 @@ _Листинг 6. Пример использования скрипта для ## Порядок выполнения задания 1. Опишите модуль `rw_instr_mem`, используя код, представленный в _листинге 4_. -2. Добавьте пакет [`bluster_pkg`](bluster_pkg.sv), содержащий объявления параметров, используемых модулем и вспомогательных вызовов, используемых тестбенчем. +2. Добавьте пакет [`bluster_pkg`](bluster_pkg.sv), содержащий объявления параметров и вспомогательных вызовов, используемых модулем и тестбенчем. 3. Опишите модуль `bluster`, используя код, представленный в _листинге 5_. Завершите описание этого модуля. 1. Опишите конечный автомат используя сигналы `state`, `next_state`, `send_fin`, `size_fin`, `flash_fin`, `next_round`. 2. [Реализуйте](#реализация-конечного-автомата) логику счетчиков `size_counter`, `flash_counter`, `msg_counter`. 3. [Реализуйте](#реализация-сигналов-подключаемых-к-uart_tx) логику сигналов `tx_valid`, `tx_data`. 4. [Реализуйте](#реализация-интерфейсов-памяти-инструкций-и-данных) интерфейсы памяти инструкций и данных. 5. [Реализуйте](#реализация-оставшейся-части-логики) логику оставшихся сигналов. -4. После описания модуля, его необходимо проверить с помощью тестового окружения. - 1. Тестбенч находится [здесь](lab_15_tb_bluster.sv). - 2. Для работы тестбенча потребуется потребуется пакет [`peripheral_pkg`](../13.%20Peripheral%20units/peripheral_pkg.sv) из ЛР№13, а также файлы [`lab_15_char.mem`](lab_15_char.mem), [`lab_15_data.mem`](lab_15_data.mem), [`lab_15_instr.mem`](lab_15_instr.mem) из папки [mem_files](mem_files). - 3. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`lab_15_tb_bluster`). - 4. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 5. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** -5. Интегрируйте программатор в модуль `riscv_unit`. - 1. Замените память инструкцией модулем `rw_instr_mem`. - 2. Добавьте модуль программатор. - 3. Подключите программатор к процессорной системе. +4. Проверьте модуль с помощью верификационного окружения, представленного в файле [`lab_15.tb_bluster.sv`](lab_15.tb_bluster.sv). В случае, если в TCL-консоли появились сообщения об ошибках, вам необходимо [найти](../../Vivado%20Basics/05.%20Bug%20hunting.md) и исправить их. + 1. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. + 2. Для работы тестбенча потребуется пакет [`peripheral_pkg`](../13.%20Peripheral%20units/peripheral_pkg.sv) из ЛР№13, а также файлы [`lab_15_char.mem`](lab_15_char.mem), [`lab_15_data.mem`](lab_15_data.mem), [`lab_15_instr.mem`](lab_15_instr.mem) из папки [mem_files](mem_files). +5. Интегрируйте программатор в модуль `processor_system`. + 1. В модуле `processor_system` замените память инструкцией модулем `rw_instr_mem`. + 2. Добавьте в модуль `processor_system` экземпляр модуля-программатора. 1. Интерфейс памяти инструкций подключается к порту записи модуля `rw_instr_mem`. 2. Интерфейс памяти данных мультиплексируется с интерфейсом памяти данных модуля `LSU`. 3. Замените сигнал сброса модуля `riscv_core` сигналом `core_reset_o`. 4. В случае если у вас есть периферийное устройство `uart_tx` его выход `tx_o` необходимо мультиплексировать с выходом `tx_o` программатора аналогично тому, как был мультиплексирован интерфейс памяти данных. -6. После интеграции модуля, его необходимо проверить с помощью тестового окружения. - 1. Тестовое окружение находится [здесь](lab_15_tb_system.sv). - 1. Данный тестбенч необходимо обновить под свой вариант. Найдите строки со вспомогательным вызовом `program_region`, первыми аргументами которого являются "YOUR_INSTR_MEM_FILE" и "YOUR_DATA_MEM_FILE". Обновите эти строки под имена файлов, которыми вы инициализировали свои память инструкций и данных в ЛР№13. Если память данных вы не инициализировали, можете удалить/закомментировать соответствующий вызов. При необходимости вы можете добавить столько вызовов, сколько вам потребуется. - 2. В .mem-файлах, которыми вы будете инициализировать вашу память необходимо сделать доработку. Вам необходимо указать адрес ячейки памяти, с которой необходимо начать инициализировать память. Это делается путем добавления в начало файла строки вида: `@hex_address`. Пример `@FA000000`. Строка обязательно должна начинаться с символа `@`, а адрес обязательно должен быть в шестнадцатеричном виде. Для памяти инструкций нужен нулевой адрес, а значит можно использовать строку `@00000000`. Для памяти данных необходимо адрес, превышающий размер памяти инструкций, но не попадающий в адресное пространство других периферийных устройств (старший байт адреса должен быть равен нулю). Поскольку система использует байтовую адресацию, адрес ячеек будет в 4 раза меньше адреса по которому обратился бы процессор. Это значит, что если бы вы хотели проинициализировать память VGA-контроллера, вам нужно было бы использовать не адрес `@07000000`, а `@01C00000` (`01C00000 * 4 = 07000000`). Таким образом, для памяти данных оптимальным адресом инициализации будет `@00200000`, поскольку эта ячейка с адресом `00200000` соответствует адресу `00800000` — этот адрес не накладывается на адресное пространство других периферийных устройств, но при этом заведомо больше возможного размера памяти инструкций. Примеры использования начальных адресов вы можете посмотреть в файлах [`lab_15_char.mem`](lab_15_char.mem), [`lab_15_data.mem`](lab_15_data.mem), [`lab_15_instr.mem`](lab_15_instr.mem) из папки [mem_files](mem_files). - 3. Тестбенч будет ожидать завершения инициализации памяти, после чего сформирует те же тестовые воздействия, что и в тестбенче к ЛР№13. А значит, если вы использовали для инициализации те же самые файлы, поведение вашей системы после инициализации не должно отличаться от поведения на симуляции в ЛР№13. - 2. Перед запуском симуляции убедитесь, что в качестве top-level модуля выбран корректный (`lab_15_tb_system`). - 3. Для запуска симуляции воспользуйтесь [`этой инструкцией`](../../Vivado%20Basics/Run%20Simulation.md). - 4. **По завершению симуляции убедитесь, что в логе есть сообщение о завершении теста!** +6. Проверьте процессорную систему после интеграции программатора с помощью верификационного окружения, представленного в файле [`lab_15.tb_processor_system.sv`](lab_15.tb_processor_system.sv). + 1. Данный тестбенч необходимо обновить под свой вариант. Найдите строки со вспомогательным вызовом `program_region`, первыми аргументами которого являются "YOUR_INSTR_MEM_FILE" и "YOUR_DATA_MEM_FILE". Обновите эти строки под имена файлов, которыми вы инициализировали свои память инструкций и данных в ЛР№13. Если память данных вы не инициализировали, можете удалить/закомментировать соответствующий вызов. При необходимости вы можете добавить столько вызовов, сколько вам потребуется. + 2. В .mem-файлах, которыми вы будете инициализировать вашу память необходимо сделать доработку. Вам необходимо указать адрес ячейки памяти, с которой необходимо начать инициализировать память. Это делается путем добавления в начало файла строки вида: `@hex_address`. Пример `@FA000000`. Строка обязательно должна начинаться с символа `@`, а адрес обязательно должен быть в шестнадцатеричном виде. Для памяти инструкций нужен нулевой адрес, а значит можно использовать строку `@00000000`. Для памяти данных необходимо адрес, превышающий размер памяти инструкций, но не попадающий в адресное пространство других периферийных устройств (старший байт адреса должен быть равен нулю). Поскольку система использует байтовую адресацию, адрес ячеек будет в 4 раза меньше адреса по которому обратился бы процессор. Это значит, что если бы вы хотели проинициализировать память VGA-контроллера, вам нужно было бы использовать не адрес `@07000000`, а `@01C00000` (`01C00000 * 4 = 07000000`). Таким образом, для памяти данных оптимальным адресом инициализации будет `@00200000`, поскольку эта ячейка с адресом `00200000` соответствует адресу `00800000` — этот адрес не накладывается на адресное пространство других периферийных устройств, но при этом заведомо больше возможного размера памяти инструкций. Примеры использования начальных адресов вы можете посмотреть в файлах [`lab_15_char.mem`](lab_15_char.mem), [`lab_15_data.mem`](lab_15_data.mem), [`lab_15_instr.mem`](lab_15_instr.mem) из папки [mem_files](mem_files). + 3. Тестбенч будет ожидать завершения инициализации памяти, после чего сформирует те же тестовые воздействия, что и в тестбенче к ЛР№13. А значит, если вы использовали для инициализации те же самые файлы, поведение вашей системы после инициализации не должно отличаться от поведения на симуляции в ЛР№13. + 4. Перед запуском моделирования, убедитесь, что у вас выбран корректный модуль верхнего уровня в `Simulation Sources`. 7. Переходить к следующему пункту можно только после того, как вы полностью убедились в работоспособности системы на этапе моделирования (увидели, что в память инструкций и данных были записаны корректные данные, после чего процессор стал обрабатывать прерывания от устройства ввода). Генерация битстрима будет занимать у вас долгое время, а итогом вы получите результат: заработало / не заработало, без какой-либо дополнительной информации, поэтому без прочного фундамента на моделировании далеко уехать у вас не выйдет. 8. Подключите к проекту файл ограничений ([nexys_a7_100t.xdc](nexys_a7_100t.xdc)), если тот еще не был подключен, либо замените его содержимое данными из файла к этой лабораторной работе. 9. Проверьте работу вашей процессорной системы на отладочном стенде с ПЛИС. @@ -603,3 +598,5 @@ _Листинг 6. Пример использования скрипта для ## Список источников 1. [Finite-state machine](https://en.wikipedia.org/wiki/Finite-state_machine). +2. [All about circuits — Finite State Machines](https://www.allaboutcircuits.com/textbook/digital/chpt-11/finite-state-machines/) +3. diff --git a/Labs/16. Coremark/Makefile b/Labs/16. Coremark/Makefile index 6734e93e..e2596a05 100644 --- a/Labs/16. Coremark/Makefile +++ b/Labs/16. Coremark/Makefile @@ -16,28 +16,22 @@ OBJDUMP = $(CC_PATH)/$(CC_PREFIX)-objdump OBJCOPY = $(CC_PATH)/$(CC_PREFIX)-objcopy SIZE = $(CC_PATH)/$(CC_PREFIX)-size -ifndef src -src = core_main.o -endif - -OBJS = $(src) startup.o core_list_join.o core_matrix.o core_portme.o core_state.o core_util.o cvt.o ee_printf.o +OBJS = core_main.o core_list_join.o core_matrix.o core_state.o core_util.o \ + barebones/core_portme.o barebones/startup.o barebones/cvt.o \ + barebones/ee_printf.o LINK_SCRIPT = linker_script.ld OUTPUT = coremark OUTPUT_PROD = $(addprefix $(OUTPUT), .mem _instr.mem _data.mem .elf _disasm.S) -INC_DIRS = "./" -SRC_DIR = ./src -CC_FLAGS = -march=rv32i_zicsr -mabi=ilp32 -I$(INC_DIRS) +INC_DIRS = $(addprefix "-I", ./barebones ./) +CC_FLAGS = -march=rv32i_zicsr -mabi=ilp32 $(INC_DIRS) LD_FLAGS = -Wl,--gc-sections -nostartfiles -T $(LINK_SCRIPT) -.PHONY: all setup clean clean_all size harvard princeton - -all: clean setup harvard +.PHONY: all clean clean_all size harvard princeton -setup: - cp barebones/*.c barebones/*.h ./ +all: clean harvard harvard: $(OUTPUT).elf $(OUTPUT)_disasm.S size # $< означает "первая зависимость" diff --git a/Labs/16. Coremark/README.md b/Labs/16. Coremark/README.md index 366d70b2..748bac09 100644 --- a/Labs/16. Coremark/README.md +++ b/Labs/16. Coremark/README.md @@ -1,4 +1,4 @@ -# Лабораторная работа 16 "Оценка производительности" +# Лабораторная работа №16 "Оценка производительности" ## Материал для подготовки к лабораторной работе @@ -18,15 +18,15 @@ ## Теория -[Coremark](https://www.eembc.org/coremark/faq.php) (далее кормарк) — это набор синтетических тестов (специальных программ) для измерения производительности процессорной системы. В данный набор входят такие тесты, как работа со связными списками, матричные вычисления, обработка конечных автоматов и подсчет контрольной суммы. Результат выражается в одном числе, которое можно использовать для сравнения с результатами других процессорных систем. +[Coremark](https://www.eembc.org/coremark/faq.php) — это набор синтетических тестов (специальных программ) для измерения производительности процессорной системы. В данный набор входят такие тесты, как работа со связными списками, матричные вычисления, обработка конечных автоматов и подсчет контрольной суммы. Результат выражается в одном числе, которое можно использовать для сравнения с результатами других процессорных систем. -Для подсчета производительности, кормарк опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер. +Для подсчета производительности, coremark опирается на функцию, возвращающую текущее время, поэтому для оценки производительности нам потребуется вспомогательное периферийное устройство: таймер. -Для вывода результатов тестирования, необходимо описать способ, которым кормарк сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из ЛР№13. +Для вывода результатов тестирования, необходимо описать способ, которым coremark сможет выводить очередной символ сообщения — для этого мы будем использовать контроллер UART из ЛР№13. Кроме того, скомпилированная без оптимизаций программа будет занимать чуть более 32KiB, поэтому нам потребуется изменить размер памяти инструкций. -Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого кормарка (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить). +Таким образом, для того чтобы запустить данную программу, нам необходимо выполнить как аппаратные изменения процессорной системы (добавить таймер и (если отсутствует) контроллер UART), так и программные изменения самого coremark (для этого в нем предусмотрены специальные платформозависимые файлы, в которых объявлены функции, реализацию которых нам необходимо выполнить). Говорят, что лучшей проверкой процессора на наличие ошибок является попытка запустить на нем ядро Linux. Наша процессорная система на это в принципе не рассчитана (поскольку для запуска Linux нужна поддержка нескольких дополнительных расширений), поэтому coremark можно по праву считать "бюджетным" аналогом проверки процессора на прочность. @@ -43,27 +43,29 @@ ### Таймер -Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счетчик (не путайте с программным счетчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сопоставимы с 32-битными значениями, поэтому системный счетчик должен быть 64-битным. Для измерения времени мы будем засекать значение счетчика на момент начала отсчета и значение счетчика в конце отсчета. Зная тактовую частоту и разность между значениями счетчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счетчик такой разрядностью, чтобы он точно не смог переполниться. +Разберемся с тем, как будет работать наш таймер. По сути, это просто системный счётчик (не путайте с программным счётчиком), непрерывно считающий такты с момента последнего сброса. Системным он называется потому, что работает на системной тактовой частоте. Значения частот, на которых работают процессорные системы сопоставимы с 32-битными значениями, поэтому системный счётчик должен быть 64-битным. Для измерения времени мы будем засекать значение счётчика на момент начала отсчета и значение счётчика в конце отсчёта. Зная тактовую частоту и разность между значениями счётчика мы с легкостью сможем вычислить прошедшее время. При этом нужно обеспечить счётчик такой разрядностью, чтобы он точно не смог переполниться. -Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счетчика. Для работы кормарка эта функциональность не нужна — если ее реализация окажется слишком сложной для вас, просто создайте системный счетчик, инкрементирующийся каждый такт, с доступом на чтение по адресу `32'h0`. +Поскольку мы уже назвали данный модуль "таймером", чтобы тот не был слишком простым, давайте добавим ему функциональности: пускай это будет устройство, способное генерировать прерывание через заданное число тактов. Таким образом, процессорная система сможет засекать время без постоянного опроса счётчика. Для работы coremark эта функциональность не нужна — если ее реализация окажется слишком сложной для вас, просто создайте системный счётчик, инкрементирующийся каждый такт, с доступом на чтение по адресу `32'h0`. Было бы удобно, чтобы мы могли управлять тем, каким образом данный модуль будет генерировать такое прерывание: однократно, заданное число раз, или же бесконечно, пока тот не остановят. -Таким образом, мы сформировали следующее адресное пространство данного контроллера: +Таким образом, мы сформировали адресное пространство контроллера, представленное в _таблице 1_. |Адрес|Режим доступа|Допустимые значения| Функциональное назначение | |-----|-------------|-------------------|---------------------------------------------------------------------------------------| -|0x00 | R | [0:2³²-1] | Значение младших 32 бит системного счетчика, доступное только для чтения | -|0x04 | R | [0:2³²-1] | Значение старших 32 бит системного счетчика, доступное только для чтения | +|0x00 | R | [0:2³²-1] | Значение младших 32 бит системного счётчика, доступное только для чтения | +|0x04 | R | [0:2³²-1] | Значение старших 32 бит системного счётчика, доступное только для чтения | |0x08 | RW | [0:2³²-1] | Указание младших 32 бит задержки, спустя которую таймер будет генерировать прерывание | |0x0c | RW | [0:2³²-1] | Указание старших 32 бит задержки, спустя которую таймер будет генерировать прерывание | |0x10 | RW | [0:2] | Указание режима генерации прерываний (выключен, заданное число раз, бесконечно) | |0x14 | RW | [0:2³²-1] | Указание количества повторений генерации прерываний | |0x24 | W | 1 | Программный сброс | -Прототип модуля следующий: +_Таблица 1. Адресное пространство -```SystemVerilog +Прототип модуля представлен в _листинге 1_. + +```Verilog module timer_sb_ctrl( /* Часть интерфейса модуля, отвечающая за подключение к системной шине @@ -84,11 +86,13 @@ module timer_sb_ctrl( ); ``` -Обратите внимание, что у модуля нет сигнала interrupt_return_i. Модуль будет генерировать прерывания ровно на 1 такт. Если процессор в этот момент не будет готов обработать прерывания (обрабатывая в этот момент какой-либо другой перехват) — запрос будет сразу же пропущен и таймер начнет отсчитывать следующий. +_Листинг 1. Прототип таймера._ + +Обратите внимание, что у модуля нет сигнала `interrupt_return_i`. Модуль будет генерировать прерывания ровно на 1 такт. Если процессор в этот момент не будет готов обработать прерывания (обрабатывая в этот момент какой-либо другой перехват) — запрос будет сразу же пропущен и таймер начнет отсчитывать следующий. Для работы данного контроллера потребуются следующие сигналы: -```SystemVerilog +```Verilog logic [63:0] system_counter; logic [63:0] delay; enum logic [1:0] {OFF, NTIMES, FOREVER} mode, next_mode; @@ -96,7 +100,7 @@ logic [31:0] repeat_counter; logic [63:0] system_counter_at_start; ``` -- `system_counter` — регистр, ассоциированный с адресами `0x00` (младшие 32 бита) и `0x04` (старшие 32 бита), системный счетчик. Задача регистра заключается в ежетактном увеличении на единицу. +- `system_counter` — регистр, ассоциированный с адресами `0x00` (младшие 32 бита) и `0x04` (старшие 32 бита), системный счётчик. Задача регистра заключается в ежетактном увеличении на единицу. - `delay` — регистр, ассоциированный с адресами `0x08` (младшие 32 бита) и `0x0c` (старшие 32 бита). Число тактов, спустя которое таймер (когда тот будет включен) сгенерирует прерывание. Данный регистр изменяется только сбросом, либо запросом на запись. - `mode` — регистр, ассоциированный с адресом `0x10`. Режим работы таймера: - `OFF` — отключен (не генерирует прерывания) @@ -104,11 +108,11 @@ logic [63:0] system_counter_at_start; - `FOREVER` — бесконечная генерация прерываний. Не отключится, пока режим работы прерываний не будет изменен. - `next_mode` — комбинационный сигнал, который подается на вход записи в регистр `mode` (аналог `next_state` из ЛР№15). Данный сигнал меняется только запросами на запись по адресу `0x10` или в случае, если `repeat_counter == 0` в режиме `NTIMES`. Поскольку этому сигналу можно присваивать только значения сигналов такого же типа (`timer_mods`), либо константы из перечисления, запросы на запись можно реализовать через блок `case` (где перебираются 3 возможных значения `write_data_i`). - `repeat_counter` — регистр, ассоциированный с адресом `0x14`. Количество повторений для режима `NTIMES`. Уменьшается в момент генерации прерывания в этом режиме в случае, если еще не равен нулю. -- `system_counter_at_start` — неархитектурный регистр, хранящий значение системного счетчика на момент начала отсчета таймера. Обновляется при генерации прерывания (если это не последнее прерывание в режиме `NTIMES`) и при запросе на запись в регистр `mode` значения не `OFF`. +- `system_counter_at_start` — неархитектурный регистр, хранящий значение системного счётчика на момент начала отсчета таймера. Обновляется при генерации прерывания (если это не последнее прерывание в режиме `NTIMES`) и при запросе на запись в регистр `mode` значения не `OFF`. Выходной сигнал interrupt_request_o должен быть равен единице, если текущий режим работы не `OFF`, а сумма `system_counter_at_start` и `delay` равна `system_counter`. -Для подключения данного таймера к системной шине, мы воспользуемся первым свободным базовым адресом, оставшимся после ЛР13: `0x08`. Таким образом, для обращения к системному счетчику, процессор будет использовать адрес `0x08000000` для обращения к регистру `delay` `0x08000008` и т.п. +Для подключения данного таймера к системной шине, мы воспользуемся первым свободным базовым адресом, оставшимся после ЛР13: `0x08`. Таким образом, для обращения к системному счётчику, процессор будет использовать адрес `0x08000000` для обращения к регистру `delay` `0x08000008` и т.п. ### Настройка Coremark @@ -125,9 +129,9 @@ logic [63:0] system_counter_at_start; #### 1. Реализация функции, измеряющей время -Не мы первые придумали измерять время путем отсчета системных тактов, поэтому вся логика по измерению времени уже реализована в coremark. От нас требуется только реализовать функцию, которая возвращает текущее значение системного счетчика. +Не мы первые придумали измерять время путем отсчета системных тактов, поэтому вся логика по измерению времени уже реализована в coremark. От нас требуется только реализовать функцию, которая возвращает текущее значение системного счётчика. -Данной функцией является `barebones_clock`, расположенная в файле [`core_portme.c`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c). В данный момент, в реализации функции описан вызов ошибки (поскольку реализации как таковой нет). Мы должны **заменить** реализацию функции следующим кодом: +Данной функцией является `barebones_clock`, расположенная в файле [`core_portme.c`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c). В данный момент, в реализации функции описан вызов ошибки (поскольку реализации как таковой нет). Мы должны **заменить** реализацию функции кодом, приведённым в _листинге 2_. ```C barebones_clock() @@ -138,9 +142,11 @@ barebones_clock() } ``` -После ЛР14 вы уже должны представлять, что здесь происходит. Мы создали указатель с абсолютным адресом `0x08000000` — адресом системного счетчика. Разыменование данного указателя вернет текущее значение системного счетчика, что и должно быть результатом вызова этой функции. Поскольку тест закончится менее чем за секунду, не обязательно загружать значение старших 32 бит (они будут не равны нулю только спустя 2³²тактов / 10⁶тактов/с ≈ 429c). +_Листинг 2. Код функции `barebones_clock`._ -Для того, чтобы корректно преобразовать тики системного счетчика во время, используется функция [`time_in_secs`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c#L117), которая уже реализована, но для работы которой нужно определить макрос `CLOCKS_PER_SEC`, характеризующий тактовую частоту, на которой работает процессор. Давайте определим данный макрос сразу над макросом [`EE_TICKS_PER_SEC`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c#L62): +После ЛР14 вы уже должны представлять, что здесь происходит. Мы создали указатель с абсолютным адресом `0x08000000` — адресом системного счётчика. Разыменование данного указателя вернет текущее значение системного счётчика, что и должно быть результатом вызова этой функции. Поскольку тест закончится менее чем за секунду, не обязательно загружать значение старших 32 бит (они будут не равны нулю только спустя 2³²тактов / 10⁶тактов/с ≈ 429c). + +Для того, чтобы корректно преобразовать тики системного счётчика во время, используется функция [`time_in_secs`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c#L117), которая уже реализована, но для работы которой нужно определить макрос `CLOCKS_PER_SEC`, характеризующий тактовую частоту, на которой работает процессор. Давайте определим данный макрос сразу над макросом [`EE_TICKS_PER_SEC`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c#L62): ```C #define CLOCKS_PER_SEC 10000000 @@ -170,13 +176,15 @@ uart_send_char(char c) } ``` +_Листинг 3. Код функции `uart_send_char_`._ + `0x06000000` — базовый адрес контроллера UART TX из ЛР13 (и адрес передаваемых этим контроллером данных). `0x08` — смещение до адреса регистра `busy` в адресном пространстве этого контроллера. #### 3. Реализация функции первичной настройки Это функция [`portable_init`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.c#L130), расположена в уже известном ранее файле [`core_portme`.c]. Данная функция выполняет необходимые нам настройки перед началом теста. Для нас главное — настроить нужным образом контроллер UART. -Допустим, мы хотим чтобы данные передавались на скорости `115200`, c одним стоповым битом и контролем бита четности. В этом случае, мы должны добавить в начало функции следующий код: +Допустим мы хотим, чтобы данные передавались на скорости `115200`, c одним стоповым битом и контролем бита четности. В этом случае, мы должны добавить в начало функции следующий код: ```C portable_init(core_portable *p, int *argc, char *argv[]) @@ -190,6 +198,8 @@ portable_init(core_portable *p, int *argc, char *argv[]) } ``` +_Листинг 4. Код функции `uart_send_char`._ + #### 4. Дополнительные настройки Для тонких настроек используется заголовочный файл [`core_portme.h`](https://github.com/eembc/coremark/blob/d5fad6bd094899101a4e5fd53af7298160ced6ab/barebones/core_portme.h), куда также требуется внести несколько изменений. Нам необходимо: @@ -200,7 +210,7 @@ portable_init(core_portable *p, int *argc, char *argv[]) ### Компиляция -Для компиляции программы, вам потребуются предоставленные файлы [Makefile](Makefile) и [linker_script.ld](linker_script.ld), а также файл [startup.S](../14.%20Programming/startup.S) из ЛР14. Эти файлы необходимо скопировать с заменой в корень папки с программой. +Для компиляции программы, вам потребуются предоставленные файлы [Makefile](Makefile) и [linker_script.ld](linker_script.ld), а также файл [startup.S](../14.%20Programming/startup.S) из ЛР№14. Эти файлы необходимо скопировать с заменой в корень папки с программой. `Makefile` написан из расчёта, что кросс-компилятор расположен по пути `C:/riscv_cc/`. В случае, если это не так, измените первую строчку данного файла в соответствии с расположением кросс-компилятора. @@ -210,27 +220,39 @@ portable_init(core_portable *p, int *argc, char *argv[]) make ``` -В случае, если на вашем рабочем компьютере не установлена утилита `make`, то вы можете скомпилировать программу вручную, выполнив следующую последовательность команд: +В случае, если на вашем рабочем компьютере не установлена утилита `make`, то вы можете скомпилировать программу вручную, выполнив последовательность команд, приведённую в _листинге 5_. ```bash -cp barebones/*.c barebones/*.h ./ -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_main.c -o core_main.o -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" startup.S -o startup.o -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_list_join.c -o core_list_join.o -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_matrix.c -o core_matrix.o -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_portme.c -o core_portme.o -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_state.c -o core_state.o -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" core_util.c -o core_util.o -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" cvt.c -o cvt.o -/c/riscv_cc/bin/riscv-none-elf-gcc -c -march=rv32i_zicsr -mabi=ilp32 -I"./" ee_printf.c -o ee_printf.o -/c/riscv_cc/bin/riscv-none-elf-gcc core_main.o startup.o core_list_join.o core_matrix.o core_portme.o core_state.o core_util.o cvt.o ee_printf.o -Wl,--gc-sections -nostartfiles -T linker_script.ld -march=rv32i_zicsr -mabi=ilp32 -I"./" -o coremark.elf -/c/riscv_cc/bin/riscv-none-elf-objdump -D coremark.elf > coremark_disasm.S -/c/riscv_cc/bin/riscv-none-elf-objcopy -O verilog --verilog-data-width=4 -j .data -j .sdata -j .bss coremark.elf coremark_data.mem -/c/riscv_cc/bin/riscv-none-elf-objcopy -O verilog --verilog-data-width=4 -j .text coremark.elf coremark_instr.mem -/c/riscv_cc/bin/riscv-none-elf-size coremark.elf -sed -i '1d' coremark_data.mem +export CC_BASE=/c/riscv_cc/bin/riscv-none-elf +export CC="$CC_BASE"-gcc +export OBJ_DUMP="$CC_BASE"-objdump +export OBJ_COPY="$CC_BASE"-objcopy +export SIZE="$CC_BASE"-size +export CC_FLAGS="-march=rv32i_zicsr -mabi=ilp32 -I./ -I./barebones" +export LD_FLAGS="-Wl,--gc-sections -nostartfiles -T linker_script.ld" +export OC_FLAGS="-O verilog --verilog-data-width=4" + +$CC -c $CC_FLAGS -o core_main.o core_main.c +$CC -c $CC_FLAGS -o startup.o startup.S +$CC -c $CC_FLAGS -o core_list_join.o core_list_join.c +$CC -c $CC_FLAGS -o core_matrix.o core_matrix.c +$CC -c $CC_FLAGS -o core_state.o core_state.c +$CC -c $CC_FLAGS -o core_util.o core_util.c +$CC -c $CC_FLAGS -o core_portme.o barebones/core_portme.c +$CC -c $CC_FLAGS -o cvt.o barebones/cvt.c +$CC -c $CC_FLAGS -o ee_printf.o barebones/ee_printf.c +$CC $CC_FLAGS $LD_FLAGS *.o -o coremark.elf + +$OBJ_DUMP -D coremark.elf > coremark_disasm.S + +$OBJ_COPY $OC_FLAGS -j .data -j .bss coremark.elf coremark_data.mem +$OBJ_COPY $OC_FLAGS -j .text coremark.elf coremark_instr.mem + +$SIZE coremark.elf ``` +_Листинг 5. Последовательность команд для компиляции coremark._ + В случае успешной компиляции, вам будет выведено сообщение об итоговом размере секций инструкций и данных: ```text @@ -246,9 +268,9 @@ sed -i '1d' coremark_data.mem ### Запуск моделирования -Программирование 32KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по старинке" через системные функции `$readmemh`. +Программирование 32KiB по UART займет ощутимое время, поэтому вам предлагается проинициализировать память инструкций и данных "по-старинке" через системные функции `$readmemh`. -Если все было сделано без ошибок, то примерно через `300ms` после снятия сигнала сброса с ядра процессора выход `tx_o` начнет быстро менять свое значение, сигнализируя о выводе результатов программы, которые отобразятся в `tcl console` примерно еще через `55ms` в виде: +Если все было сделано без ошибок, то примерно через `300ms` после снятия сигнала сброса с ядра процессора выход `tx_o` начнет быстро менять свое значение, сигнализируя о выводе результатов программы, которые отобразятся в `tcl console` примерно еще через `55ms` в виде _листинга 6_ (вывод сообщения будет завершен приблизительно на `355ms` времени моделирования). ```text 2K performance run parameters for coremark. @@ -269,20 +291,20 @@ seedcrc : 0x29f4 Errors detected ``` -(вывод сообщения будет завершен приблизительно на `355ms` времени моделирования). +_Листинг 6. Лог вывода результатов coremark. Значения "Total time (secs)" и "Iterations/Sec" скрыты до получения результатов моделирования._ ## Порядок выполнения задания 1. [Опишите](#таймер) таймер в виде модуля `timer_sb_ctrl`. -2. Проверьте описанный модуль с помощью тестового окружения [lab_16_tb_timer](lab_16_tb_timer.sv). -3. Подключите `timer_sb_ctrl` к системной шине. - 1. Ко входу `rst_i` модуля подключите сигнал `core_reset_o` программатора. Таким образом, системный счетчик начнет работать только когда память системы будет проинициализирована. - 2. Сигнал прерывания этого модуля подключать не обязательно, т.к. кормарк будет осуществлять чтение путем опроса системного счетчика, а не по прерыванию. -4. В случае, если до этого в ЛР13 вашим устройством вывода было не UART TX, вам необходимо подключить к системной шине готовый модуль [uart_tx_sb_ctrl](../Made-up%20modules/lab_13.uart_tx_sb_ctrl.sv). -5. Получите исходники программы Coremark. Для этого можно либо склонировать [репозиторий](https://github.com/eembc/coremark/tree/d5fad6bd094899101a4e5fd53af7298160ced6ab), либо скачать его в виде архива. +2. Проверьте модуль с помощью верификационного окружения, описанного в файле [lab_16.tb_timer.sv](lab_16.tb_timer.sv). +3. Интегрируйте модуль `timer_sb_ctrl` в процессорную систему. + 1. Ко входу `rst_i` модуля подключите сигнал `core_reset_o` программатора. Таким образом, системный счётчик начнет работать только когда память системы будет проинициализирована. + 2. Сигнал прерывания этого модуля подключать не обязательно, т.к. coremark будет осуществлять чтение путем опроса системного счётчика, а не по прерыванию. +4. В случае, если до этого в Л№Р13 вашим устройством вывода было не UART TX, вам необходимо подключить к системной шине готовый модуль [uart_tx_sb_ctrl](../Made-up%20modules/lab_13.uart_tx_sb_ctrl.sv). +5. Получите исходники программы coremark. Для этого можно либо склонировать [репозиторий](https://github.com/eembc/coremark/tree/d5fad6bd094899101a4e5fd53af7298160ced6ab), либо скачать его в виде архива. 6. Добавьте реализацию платформозависимых функций программы coremark. Для этого в папке `barebones` необходимо: 1. в файле `core_portme.c`: - 1. [реализовать](#1-реализация-функции-измеряющей-время) функцию `barebones_clock`, возвращающую текущее значение системного счетчика; + 1. [реализовать](#1-реализация-функции-измеряющей-время) функцию `barebones_clock`, возвращающую текущее значение системного счётчика; 2. объявить макрос `CLOCKS_PER_SEC`, характеризующий тактовую частоту процессора; 3. [реализовать](#3-реализация-функции-первичной-настройки) функцию `portable_init`, выполняющую первичную инициализацию периферийных устройств до начала теста; 2. в файле `ee_printf.c` [реализовать](#2-реализация-вывода-очередного-символа-сообщения) функцию `uart_send_char`, отвечающую за отправку очередного символа сообщения о результате. @@ -291,12 +313,12 @@ Errors detected 1. Если кросскомпилятор расположен не в директории `C:/riscv_cc`, перед вызовом `make` вам необходимо соответствующим образом отредактировать первую строчку в `Makefile`. 2. В случае отсутствия на компьютере утилиты `make`, вы можете самостоятельно скомпилировать программу вызовом команд, представленных в разделе ["Компиляция"](#компиляция). 9. Временно измените размер памяти инструкций до 64KiB, а памяти данных до 16KiB, изменив значение параметров `INSTR_MEM_SIZE_BYTES` и `DATA_MEM_SIZE_BYTES` в пакете `memory_pkg` на `32'h10_000` и `32'h4_000` соответственно. -10. Проинициализируйте память инструкций и память данных файлами "coremark_instr.mem" и "coremark_data.mem", полученными в ходе компиляции программы. - 1. Память можно проинициализировать двумя путями: с помощью вызова системной функции $readmemh, либо же с помощью программатора.Однако имейте в виду, что инициализация памятей с помощью программатора будет достаточно долго моделироваться в виду большого объема программы. - 2. В случае, если инициализация будет осуществляться посредством $readmemh, не забудьте удалить первую строчку со стартовым адресом из файла, инициализирующего память данных. +10. Проинициализируйте память инструкций и память данных файлами `coremark_instr.mem` и `coremark_data.mem`, полученными в ходе компиляции программы. + 1. Память можно проинициализировать двумя путями: с помощью вызова системной функции `$readmemh`, либо же с помощью программатора. Однако имейте в виду, что инициализация памятей с помощью программатора будет достаточно долго моделироваться в виду большого объема программы. + 2. В случае, если инициализация будет осуществляться посредством `$readmemh`, не забудьте удалить первую строчку со стартовым адресом из файла, инициализирующего память данных. 3. В случае, если инициализация будет осуществляться с помощью программатора, используйте вспомогательные вызовы `program_region` из пакета `bluster_pkg`, как это было сделано в `lab_15_tb_system`. - 4. В исходном виде тестбенч описан под инициализацию памяти посредством $readmemh. -11. Выполните моделирование системы с помощью модуля [tb_coremark](tb_coremark). + 4. В исходном виде тестбенч описан под инициализацию памяти посредством `$readmemh`. +11. Выполните моделирование системы с помощью модуля [lab_16.tb_coremark](lab_16.tb_coremark). 1. Результаты теста будут выведены приблизительно на `355ms` времени моделирования. ## Оценка производительности @@ -304,7 +326,7 @@ Errors detected
Прочти меня после успешного завершения моделирования -Итак, вы получили сообщение вида: +Итак, вы получили сообщение, представленное в _листинге 7_. ```text 2K performance run parameters for coremark. @@ -325,6 +347,8 @@ seedcrc : 0xe9f5 Errors detected ``` +_Листинг 7. Лог вывода результатов coremark._ + Не обращайте внимание на строки "ERROR! Must execute for at least 10 secs for a valid result!" и "Errors detected". Программа считает, что для корректных результатов, необходимо крутить ее по кругу в течении минимум 10 секунд, однако по большей части это требование необходимо для более достоверного результата у систем с кэшем/предсказателями переходов и прочими блоками, которые могут изменить количество тактов на прохождение между итерациями. Наш однотактный процессор будет вести себя одинаково на каждом круге, поэтому нет смысла в дополнительном времени моделирования. Тем не менее, если вы захотите получить результаты, не содержащих сообщения об ошибках, измените число итераций в файле `core_portme.h` до 45. Нас интересует строка: @@ -335,9 +359,9 @@ Iterations/Sec : 3.446111 Это и есть так называемый "кормарк" — метрика данной программы. Результат нашего процессора: ~3.45 кормарка. -Обычно, для сравнения между собой нескольких реализаций микроархитектур, более достоверной считается величина "кормарк / МГц", т.е. значение кормарка, поделённое на тактовую частоту процессора, поскольку время прохождения теста напрямую зависит от тактовой частоты. Это значит, что чип с менее удачной микроархитектурной реализацией может выиграть по кормарку просто потому что он был выпущен по лучшей технологии, позволяющей запустить его на больших частотах. Кормарк/МГц нормализует результаты, позволяя сравнивать микроархитектурные решения не заботясь о том, на какой частоте был получен результат. +Обычно, для сравнения между собой нескольких реализаций микроархитектур, более достоверной считается величина "кормарк / МГц", т.е. число кормарков, поделённое на тактовую частоту процессора, поскольку время прохождения теста напрямую зависит от тактовой частоты. Это значит, что чип с менее удачной микроархитектурной реализацией может выиграть по кормарку просто потому, что он был выпущен по лучшей технологии, позволяющей запустить его на больших частотах. Кормарк/МГц нормализует результаты, позволяя сравнивать микроархитектурные решения, не заботясь о том, на какой частоте был получен результат. -Более того, при сравнении с другими результатами, необходимо учитывать флаги оптимизации, которые использовались при компиляции программы, поскольку они также влияют на результат. Например, если собрать кормарк с уровнем оптимизаций `-O1`, результат нашей системы скакнет до 11.23 кормарков, что всего лишь является следствием того, что программа стала меньше обращаться к памяти вследствие оптимизаций. Именно поэтому результаты кормарка указываются вместе с опциями, с которыми тот был собран. +Более того, при сравнении с другими результатами, необходимо учитывать флаги оптимизации, которые использовались при компиляции программы, поскольку они также влияют на результат. Например, если собрать coremark с уровнем оптимизаций `-O1`, результат нашей системы скакнёт до 11.23 кормарков, что всего лишь является следствием того, что программа стала меньше обращаться к памяти вследствие оптимизаций. Именно поэтому результаты coremark указываются вместе с опциями, с которыми тот был собран. Мы не будем уходить в дебри темных паттернов маркетинга и вместо этого будет оценивать производительность в лоб: сколько кормарков в секунду смог прогнать наш процессор без каких-либо оптимизаций в сравнении с представленными результатами других систем вне зависимости от их оптимизаций. @@ -353,7 +377,7 @@ Iterations/Sec : 3.446111 А знаете, с чем был сопоставим по производительности компьютер TRS-80? С бортовым компьютером [Apollo Guidance Computer](https://en.wikipedia.org/wiki/Apollo_Guidance_Computer), который проводил вычисления и контролировал движение, навигацию, управлял командным и лунным модулями в ходе полётов по программе Аполлон. -Иными словами, мы разработали процессор, который приблизительно в 7-14 раз производительнее компьютера, управлявшего полетом космического корабля, который доставил человека на Луну! +Иными словами, мы разработали процессор, который приблизительно в 7-14 раз производительнее компьютера, управлявшего полётом космического корабля, который доставил человека на Луну! Можно ли как-то улучшить наш результат? Безусловно. Мы можем улучшить его примерно на 5% изменив буквально одну строчку. Дело в том, что для простоты реализации, мы генерировали сигнал `stall` для каждой операции обращения в память. Однако приостанавливать работу процессора было необходимо только для операций чтения из памяти. Если не генерировать сигнал `stall` для операций типа `store`, мы уменьшим время, необходимое на исполнение бенчмарка. Попробуйте сделать это сами. diff --git a/Labs/16. Coremark/tb_coremark.sv b/Labs/16. Coremark/lab_16.tb_coremark.sv similarity index 99% rename from Labs/16. Coremark/tb_coremark.sv rename to Labs/16. Coremark/lab_16.tb_coremark.sv index d1dac7f5..9596500b 100644 --- a/Labs/16. Coremark/tb_coremark.sv +++ b/Labs/16. Coremark/lab_16.tb_coremark.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_coremark(); +module lab_16_tb_coremark(); logic clk100mhz_i; logic aresetn_i; diff --git a/Labs/16. Coremark/tb_timer.sv b/Labs/16. Coremark/lab_16.tb_timer.sv similarity index 99% rename from Labs/16. Coremark/tb_timer.sv rename to Labs/16. Coremark/lab_16.tb_timer.sv index 27b67b18..2b02c31a 100644 --- a/Labs/16. Coremark/tb_timer.sv +++ b/Labs/16. Coremark/lab_16.tb_timer.sv @@ -8,7 +8,7 @@ See https://github.com/MPSU/APS/blob/master/LICENSE file for licensing details. * ------------------------------------------------------------------------------ */ -module tb_timer(); +module lab_16_tb_timer(); logic clk_i; logic rst_i; diff --git a/Labs/README.md b/Labs/README.md index 70dc7a74..92b33db2 100644 --- a/Labs/README.md +++ b/Labs/README.md @@ -30,7 +30,7 @@ ## Полезное - [Студенческий сервер](../Other/Students%20server.md) -- [Создание базового проекта с прошивкой ПЛИС в Vivado](../Vivado%20Basics/Vivado%20trainer.md) +- [Создание базового проекта с прошивкой ПЛИС в Vivado](../Vivado%20Basics/01.%20New%20project.md) - [Базовые конструкции Verilog](../Basic%20Verilog%20structures/) - [Список типичных ошибок в Vivado и SystemVerilog](../Other/FAQ.md) - [Тестовое окружение](../Basic%20Verilog%20structures/Testbench.md) diff --git a/Lectures/10. Pipeline processor.md b/Lectures/10. Pipeline processor.md index 7e822b5d..6257da1f 100644 --- a/Lectures/10. Pipeline processor.md +++ b/Lectures/10. Pipeline processor.md @@ -18,7 +18,7 @@ 4. Memory (обращение к памяти данных. Включает в себя Data Memory) 5. Writeback (запись результата в регистровый файл. Включает в себя мультиплексор и порт записи Register File) -Не смотря на главный плюс конвейерной реализации (скорость), ее главным минусом является необходимость устранения конфликтов. Конфликт конвейера — это ситуация, при которой конвейер должен приостановиться, чтобы выполнение очередной инструкции не нарушило работу программы. Они возникают из-за параллельного выполнения нескольких инструкций на разных стадиях конвейера, так как из-за зависимостей не любые из них могут обрабатываться параллельно. Например, типичным конфликтом конвейера является конфликт по данным, который может возникнуть, если очередная инструкция пытается считать данные из регистрового файла, которые предшествующая инструкция еще не успела там разместить. +Несмотря на главный плюс конвейерной реализации (скорость), ее главным минусом является необходимость устранения конфликтов. Конфликт конвейера — это ситуация, при которой конвейер должен приостановиться, чтобы выполнение очередной инструкции не нарушило работу программы. Они возникают из-за параллельного выполнения нескольких инструкций на разных стадиях конвейера, так как из-за зависимостей не любые из них могут обрабатываться параллельно. Например, типичным конфликтом конвейера является конфликт по данным, который может возникнуть, если очередная инструкция пытается считать данные из регистрового файла, которые предшествующая инструкция еще не успела там разместить. ![../.pic/Lectures/10.%20Pipeline%20processor/fig_03.png](../.pic/Lectures/10.%20Pipeline%20processor/fig_03.png) diff --git a/Lectures/12. Productivity techniques.md b/Lectures/12. Productivity techniques.md index f38dadfc..af37222e 100644 --- a/Lectures/12. Productivity techniques.md +++ b/Lectures/12. Productivity techniques.md @@ -102,7 +102,7 @@ - Временной - Пространственный -Не смотря на то, что конвейерный процессор за один так выдает результат лишь одной инструкции, на каждом такте в процессе выполнения находятся несколько инструкций одновременно, но на разных стадиях. Это и называется **временным параллелизмом**. +Несмотря на то, что конвейерный процессор за один так выдает результат лишь одной инструкции, на каждом такте в процессе выполнения находятся несколько инструкций одновременно, но на разных стадиях. Это и называется **временным параллелизмом**. Примером **пространственного параллелизма** может служить принцип работы _суперскалярного процессора_, т.к. за счет наличия одинаковых функциональных устройств на них одновременно могут выполняться различные инструкции. diff --git a/Other/CSR.md b/Other/CSR.md index 055cc78a..78791da1 100644 --- a/Other/CSR.md +++ b/Other/CSR.md @@ -10,7 +10,7 @@ _Таблица 1. Регистры контроля и статуса машинного (наивысшего) уровня привилегий._ -Для работы с CS-регистрами используются специальные **SYSTEM**-инструкции I-типа (с опкодом `1110011`), хранящие в 12-битном поле **imm** адрес регистра, к которому будет осуществлен доступ и адреса в регистровом файле откуда будет считан или куда будет записан один из CS-регистров. Вы уже добавляли поддержку этих инструкций во время выполнения [лабораторной работы №5](../05.%20Main%20decoder/) "Декодер инструкций". +Для работы с CS-регистрами используются специальные **SYSTEM**-инструкции I-типа (с опкодом `1110011`), хранящие в 12-битном поле **imm** адрес регистра, к которому будет осуществлен доступ и адреса в регистровом файле откуда будет считан или куда будет записан один из CS-регистров. | opcode | func3 | Тип | Инструкция | Описание | Операция | |--------|-------|-----|---------------------|---------------------------|-----------------------------| diff --git a/Other/rv32i.md b/Other/rv32i.md index 810fb360..999ecfea 100644 --- a/Other/rv32i.md +++ b/Other/rv32i.md @@ -7,7 +7,7 @@ - [Псевдоинструкции](#псевдоинструкции) - [Основные типы команд](#основные-типы-команд) -> Большая часть данного документа в той или иной степени является переводом спецификации RISC-V[[1]](https://github.com/riscv/riscv-isa-manual), распространяемой по лицензии [CC-BY-4.0 ](https://creativecommons.org/licenses/by/4.0/). +> Большая часть данного документа в той или иной степени является переводом спецификации RISC-V[[1]](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf), распространяемой по лицензии [CC-BY-4.0 ](https://creativecommons.org/licenses/by/4.0/). ## Краткая справка по RISC-V и RV32I @@ -53,7 +53,7 @@ _Рисунок 1. Основные компоненты архитектуры Как было сказано ранее, память имеет 32-битную шину адреса и имеет побайтовую адресацию. Это значит, что каждый из 232 байт памяти имеет свой уникальный адрес, по которому к нему можно обратиться, чтобы считать из него или записать в него новую информацию. Однако, инструкции кодируются 32-битными числами, а один байт это всего 8 бит, значит одна инструкция занимает сразу 4 адреса в памяти. Подразумевается, что из такой памяти можно читать одновременно из нескольких последовательных адресов, то есть устройство управления процессора сообщает памяти начальный адрес требуемой ячейки, и количество ячеек (одну, две или четыре), которые нужно прочитать или записать. -Одна ячейка называется `байт` — 8 бит. Две последовательные 8-битные ячейки называются `полуслово` — 16 бит. Четыре последовательные 8-битные ячейки называются `словом` — 32 бита. Например, если процессор собирается выполнить инструкцию, которая занимает четыре байта по адресам `0x00000007 — 0x00000004`, то он обращается к памяти, сообщая, что "нужны 4 байта начиная с адреса 0x00000004", взамен процессор получает 32-битное число — инструкцию, которая была слеплена из байт, хранящихся в памяти по адресам: 4, 5, 6 и 7, для данного примера. К памяти также можно обратиться за полусловом или за байтом. Предполагается реализация выровненного доступа к памяти, то есть адреса слов и полуслов должны быть кратны 4 и 2, соответственно. +Одна ячейка памяти, содержащая 8 бит называется `байт`. Две последовательные 8-битные ячейки называются `полусловом` — 16 бит. Четыре последовательные 8-битные ячейки называются `словом` — 32 бита. Если процессор собирается выполнить инструкцию, которая занимает четыре байта по адресам `0x00000007 — 0x00000004`, то он обращается к памяти, сообщая, что "нужны 4 байта начиная с адреса `0x00000004`", в ответ процессор получает 32-битное число — инструкцию, которая была "склеена" из байт, хранящихся в памяти по адресам: 4, 5, 6 и 7, для данного примера. К памяти также можно обратиться за полусловом или за байтом. Предполагается реализация выровненного доступа к памяти, то есть адреса слов и полуслов всегда должны быть кратны 4 и 2, соответственно. Аппаратное обеспечение компьютера «понимает» только нули и единицы, поэтому инструкции закодированы двоичными числами в формате, который называется машинным языком. @@ -73,7 +73,7 @@ _Таблица 1. Инструкции набора RV32I с приведени Обратите внимание на операции `slli`, `srli` и `srai` (операции сдвига на константную величину). У этих инструкций немного измененный формат кодирования **I\***. Формат кодирования **I** предоставляет 12-битную константу. Сдвиг 32-битного числа более, чем на 31 не имеет смысла. Для кодирования числа 31 требуется всего 5 бит. Выходит, что из 12 бит константы используется только 5 бит для операции сдвига, а оставшиеся 7 бит – не используются. А, главное (какое совпадение!), эти 7 бит находятся ровно в том же месте, где у других инструкций находится поле `Func7`. Поэтому, чтобы у инструкций `slli`, `srli` и `srai` использующих формат **I** не пропадала эта часть поля, к ней относятся как к полю `Func7`. -Таблица 2 является фрагментом [`оригинальной спецификации RISC-V`](https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf). Сверху приводятся 6 форматов кодирования инструкций: **R**, **I**, **S**, **B**, **U** и **J**, а ниже приводятся конкретные значения полей внутри инструкции. Под `rd` подразумевается 5-битный адрес регистра назначения, `rs1` и `rs2` - 5-битные адреса регистров источников, `imm` — константа, расположение и порядок битов которой указывается в квадратных скобках. Обратите внимание, что в разных форматах кодирования константы имеют различную разрядность, а их биты упакованы по-разному. Для знаковых операций константу предварительно знаково расширяют до 32 бит. Для беззнаковых расширяют нулями до 32 бит. +Таблица 2 является фрагментом [`оригинальной спецификации RISC-V`](https://github.com/riscv/riscv-isa-manual/releases/download/20240411/unpriv-isa-asciidoc.pdf). Сверху приводятся 6 форматов кодирования инструкций: **R**, **I**, **S**, **B**, **U** и **J**, а ниже приводятся конкретные значения полей внутри инструкции. Под `rd` подразумевается 5-битный адрес регистра назначения, `rs1` и `rs2` - 5-битные адреса регистров источников, `imm` — константа, расположение и порядок битов которой указывается в квадратных скобках. Обратите внимание, что в разных форматах кодирования константы имеют различную разрядность, а их биты упакованы по-разному. Для знаковых операций константу предварительно знаково расширяют до 32 бит. Для беззнаковых расширяют нулями до 32 бит. ![../.pic/Labs/lab_05_decoder/rv32i_BIS.png](../.pic/Labs/lab_05_decoder/rv32i_BIS.png) @@ -135,7 +135,7 @@ _Рисунок 5. Иллюстрация общих частей при код Большинство инструкций целочисленных вычислений работают с 32-битными значениями, хранящимся в регистровом файле. Такие команды либо кодируются как операции `константа-регистр`, используя формат **I**-типа, либо как операции `регистр-регистр`, используя формат **R**-типа. В обоих случаях результат сохраняется в регистр `rd` . Ни одна инструкция целочисленных вычислений не вызывает арифметических исключений. -> Мы стали добавлять поддержку специального набора команд для проверок на переполнение целочисленных арифметических операций в основной набор команд, поскольку многие проверки на переполнение могут быть достаточно дешево реализованы в RISC-V с использованием инструкций ветвления. Проверка на переполнение для беззнакового сложения требует только одной дополнительной команды перехода после сложения: +> Мы не стали добавлять поддержку специального набора команд для проверок на переполнение целочисленных арифметических операций в основной набор команд, поскольку многие проверки на переполнение могут быть достаточно дешево реализованы в RISC-V с использованием инструкций ветвления. Проверка на переполнение для беззнакового сложения требует только одной дополнительной команды перехода после сложения: > > ```asm > add t0, t1, t2 @@ -145,8 +145,8 @@ _Рисунок 5. Иллюстрация общих частей при код > Для знакового сложения, если известен знак одного операнда, проверка на переполнение требует только одного ветвления после сложения: > > ```asm -> addi t0, t1, +imm; -> blt t0, t1, overflow. +> addi t0, t1, + imm +> blt t0, t1, overflow > ``` > > Этот метод в общем случае подходит при сложении с непосредственным операндом. В остальных случаях при знаковом сложении требуются три дополнительные команды после сложения, использующих утверждение, что сумма должна быть меньше, чем один из операндов, тогда и только тогда, когда другой операнд отрицателен. @@ -166,9 +166,9 @@ _Рисунок 5. Иллюстрация общих частей при код `ADDI` суммирует знакорасширенную 12-битную константу с регистром `rs1`. Арифметическое переполнение игнорируется, и результатом являются младшие 32 бита результата. Команда `ADDI rd, rs1, 0` используется для реализации ассемблерной псевдоинструкции `MV rd, rs1`. -`SLTI` (установить, если меньше чем константа) помещает значение 1 в регистр `rd`, если регистр `rs1` меньше, чем расширенное непосредственное значение, когда оба значения обрабатываются как знаковые числа, иначе в `rd` записывается 0. `SLTIU` аналогична, но сравнивает значения как беззнаковые числа (то есть непосредственное значение сначала расширяется до 32 бит, а затем обрабатывается как число без знака). Обратите внимание, что команда `SLTIU rd, rs1, 1` устанавливает `rd` в 1, если `rs1` равен нулю, в противном случае `rd` устанавливается в 0 (псевдоинструкция ассемблера `SEQZ rd, rs`). +`SLTI` (установить, если меньше, чем константа) помещает значение 1 в регистр `rd`, если регистр `rs1` меньше, чем расширенное непосредственное значение, когда оба значения обрабатываются как знаковые числа, иначе в `rd` записывается 0. `SLTIU` аналогична, но сравнивает значения как беззнаковые числа (то есть непосредственное значение сначала расширяется до 32 бит, а затем обрабатывается как число без знака). Обратите внимание, что команда `SLTIU rd, rs1, 1` устанавливает `rd` в 1, если `rs1` равен нулю, в противном случае `rd` устанавливается в 0 (псевдоинструкция ассемблера `SEQZ rd, rs`). -Примечание: у студентов часто возникает вопрос: зачем вообще нужны инструкции вида `SLT`, если есть инструкции вида `BLT`? Например, они могут использоваться для вычисления сложных условий переходов. Один из примеров таких условий вы видели выше, в примере обработке результата сложения на переполнение. Кроме того, не смотря на ограниченность этих инструкций (все они проверяют только на **строго меньше**), мы можем добиться операции **строго больше** поменяв операнды местами, а если результат обоих операций даст `0` — значит операнды равны. Поскольку идея RISC архитектуры в том, чтобы переложить организацию всех этих ухищрений на компилятор, этих инструкций оказывается достаточно. +Примечание: у студентов часто возникает вопрос: зачем вообще нужны инструкции вида `SLT`, если есть инструкции вида `BLT`? Например, они могут использоваться для вычисления сложных условий переходов. Один из примеров таких условий вы видели выше, в примере обработке результата сложения на переполнение. Кроме того, несмотря на ограниченность этих инструкций (все они проверяют только на **строго меньше**), мы можем добиться операции **строго больше** поменяв операнды местами, а если результат обоих операций даст `0` — значит операнды равны. Поскольку идея RISC архитектуры в том, чтобы переложить организацию всех этих ухищрений на компилятор, этих инструкций оказывается достаточно. `ANDI`, `ORI`, `XORI` — это логические операции, которые выполняют побитовое И, ИЛИ и исключающее ИЛИ над регистром `rs1` и непосредственным 12-битным значением с знаковым расширением и помещают результат в `rd`. Обратите внимание, что команда `XORI rd, rs, -1` выполняет побитовую логическую инверсию значения регистра `rs1` (псевдоинструкция `NOT rd, rs`). diff --git a/README.md b/README.md index 3d19eec5..1bbd4bd8 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Под словом Архитектура понимается некоторый способ организации. Процессор – это программно-управляемое устройство для обработки информации. Проще говоря, это устройство, управлять поведением которого можно с помощью программ (последовательности команд/действий). Система – это комбинация взаимодействующих элементов, организованных для достижения поставленных целей. Таким образом, дисциплина "Архитектур процессорных систем" посвящена способам организации и построения систем под управлением устройств управляемых программами. Большое внимание в курсе уделяется открытой, и очень популярной в настоящее время, процессорной архитектуре RISC-V. -Дисциплина реализуется Институтом МПСУ на базе НИУ МИЭТ сразу для 7 различных направлений подготовки, которые имеют разные названия и количество теоретического и практического материалов. Не смотря на это масштаб покрытия у них одинаковый, а суть предмета изучения общая - организация компьютеров. Отличаются лишь глубина погружения и акценты. +Дисциплина реализуется Институтом МПСУ на базе НИУ МИЭТ сразу для 7 различных направлений подготовки, которые имеют разные названия и количество теоретического и практического материалов. Несмотря на это масштаб покрытия у них одинаковый, а суть предмета изучения общая - организация компьютеров. Отличаются лишь глубина погружения и акценты. Для успешного погружения в дисциплину важно понимать зачем эта дисциплина нужна именно тебе, будучи студентом: diff --git a/Vivado Basics/01. New project.md b/Vivado Basics/01. New project.md new file mode 100644 index 00000000..f90160a2 --- /dev/null +++ b/Vivado Basics/01. New project.md @@ -0,0 +1,44 @@ +# Создание нового проекта в Vivado + +Для того, чтобы создать новый проект в Vivado для отладочного стенда Nexys A7, следуйте следующему порядку выполнения действий. + +1. Запустите Vivado. +2. Нажмите `Create Project`. +3. В открывшемся окне нажмите `Next`. +4. Введите название проекта (никаких пробелов и кириллических символов) → Выберите папку для проектов → Установите селектор `Create project subdirectory` → Нажмите `Next`. +5. Выберите RTL Project → Установите селектор `Do not specify sources at this time` → Нажмите `Next`. +6. Выставьте следующие фильтры, чтобы сузить список ПЛИС: + - Family: `Artix 7` + - Package: `csg324`, + - Speed: `-1`. + + На _рис. 1_ показано окно с примененными фильтрами. +7. В списке выберите ПЛИС `xc7a100tcsg324-1` (расположена в самом низу) → Нажмите `Next`. +8. Нажмите `Finish` + +![../.pic/Vivado%20Basics/01.%20New%20project/fig_01.png](../.pic/Vivado%20Basics/01.%20New%20project/fig_01.png) + +_Рисунок 1. Пример заполнения фильтров для выбора ПЛИС, используемой в Nexys A7._ + +После нажатия на `Finish`, откроется окно созданного проекта. Выполним его настройку. Для этого, в окне `Flow Navigator`, расположенном в левой части Vivado необходимо нажать на кнопку `Settings`. + +В первую очередь, нам необходимо указать какое количество времени работы схемы будет моделироваться при запуске симуляции. Для этого, в группе `Project Settings` необходимо выбрать `Simulation`. В открывшейся странице выбрать вкладку `Simulation`, и в поле `xsim.simulate.runtime` указать значение `1s`, что означает, что по умолчанию будет запускаться симуляция одной секунды времени работы схемы. На _рис. 2_. показан пример данной настройки. Пока что не закрывайте окно настроек. + +![../.pic/Vivado%20Basics/01.%20New%20project/fig_02.png](../.pic/Vivado%20Basics/01.%20New%20project/fig_02.png) + +_Рисунок 2. Пример настройки времени симуляции._ + +Одна секунда — это очень большое значение, на многие порядки превышающее время симуляции в большинстве лабораторных работ. Однако верификационное окружение во всех лабораторных будет досрочно останавливать моделирование. Установив подобное большое значение, мы избавимся от необходимости указывать нужное нам время симуляции при каждой симуляции: она просто будет идти, пока не остановится, но в случае, если верификационное окружение почему-то не остановит моделирование, мы будем знать, что оно остановится само по достижении времени в 1с. + +Выполним также настройку этапа предобработки (пункт `Elaboration` в группе `Project Settings`). Здесь необходимо установить переключатель в положение `Blackbox model (Stub file)`. На самом деле, в курсе лабораторных работ мы не будем пользоваться ничем, на что влияет эта настройка, однако установив переключатель в данное положение, мы отключим появление ненужного информационного окна каждый раз, когда мы будем выполнять предобработку проекта. После выполнения данной настройки можно нажать на `OK`. + +![../.pic/Vivado%20Basics/01.%20New%20project/fig_03.png](../.pic/Vivado%20Basics/01.%20New%20project/fig_03.png) + +_Рисунок 3. Пример настройки предобработки проекта._ + +Все эти настройки можно сделать в автоматическом режиме, введя следующие команды в поле для ввода `Tcl Console`, помеченном текстом `Type a Tcl command here`: + +```tcl +set_property -name {xsim.simulate.runtime} -value {1s} -objects [get_filesets sim_1] +set_property elab_link_dcps false [current_fileset] +``` diff --git a/Vivado Basics/02. Flow Navigator.md b/Vivado Basics/02. Flow Navigator.md new file mode 100644 index 00000000..c1bfa8e3 --- /dev/null +++ b/Vivado Basics/02. Flow Navigator.md @@ -0,0 +1,24 @@ +# Навигатор по маршруту проектирования (Flow Navigatior) + +После создания нового проекта, откроется основное окно проекта Vivado, представленного на _рис. 1_. + +![../.pic/Vivado%20Basics/02.%20Flow%20Navigator/fig_01.png](../.pic/Vivado%20Basics/02.%20Flow%20Navigator/fig_01.png) + +_Рисунок 1. Окно пустого проекта Vivado._ + +Визуально, основное окно Vivado разделено на две части: тонкую полоску окна `Flow Navigator`, расположенную слева, и основное окно `Project Manager`, расположенную справа. Дело в том, что Vivado представляет собой мощную многофункциональную интегрированную среду разработки (Integrated Design Environment, IDE), состоящую из нескольких подпрограмм, которые для удобства упакованы в одну общую графическую оболочку. `Flow Navigator` позволяет переключаться между этими подпрограммами в рамках одного окна. + +Маршрут проектирования подразумевает цикличное повторение шагов: + +1. Написание кода. +2. Анализ получившейся схемы на предмет легко обнаруживаемых ошибок. +3. Симуляция схемы +4. Синтез +5. Имплементация +6. Генерация двоичного кода для прошивки ПЛИС + +Подробнее о некоторых из этапов рассказано в главе "Этапы реализации проекта в ПЛИС". + +Если на каком-то из этапов обнаруживается ошибка, происходит возврат на шаг 1 и повторение всего процесса заново. Благодаря `Flow Navigator` вам не нужно постоянно запускать для каждого этапа новую программу, вы можете быстро переходить от этапа к этапу в рамках одного графического окна. + +При этом, в зависимости от того, какую именно подпрограмму вы активировали во `Flow Navigator`, соответствующим образом изменится основная часть окна Vivado. После создания проекта автоматически активируется программа `Project Manager` (выделено бирюзовым в окне `Flow Navigator`), именно поэтому в основном окне открылся менеджер проекта. diff --git a/Vivado Basics/03. Project manager.md b/Vivado Basics/03. Project manager.md new file mode 100644 index 00000000..f39d2da1 --- /dev/null +++ b/Vivado Basics/03. Project manager.md @@ -0,0 +1,351 @@ +# Менеджер проекта (Project Manager) + +Окно Project Manager позволяет управлять проектом: добавлять и редактировать исходные коды проекта, изучить краткое ревью по утилизации ресурсов ПЛИС, используемых для реализации проекта, просматривать логи и сообщения о результатах сборки проекта и многое другое. + +В первую очередь нас интересует окно исходных кодов проекта, которое называется `Design Sources` и представлено на _рис. 1_. + +## Окно Design Sources + +Данное окно находится по умолчанию в верхнем левом углу окна Project Manager (в случае, если вы случайно закрыли это окно, вы можете вернуть его обратно через меню `Windows->Sources`). + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_01.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_01.png) + +_Рисунок 1. Окно исходных кодов проекта._ + +Данное окно разделено на три вкладки: + +1. Иерархия (Hierarchy) +2. Библиотеки (Libraries) +3. Порядок сборки (Compile Order) + +В определенных ситуациях в данном окне может появиться и вкладка IP Cores, но в рамках данного курса она нас не интересует. + +Рассмотрим по порядку данные вкладки. + +### Вкладка Hierarchy + +Данная вкладка состоит из четырех "папок": + +1. Design Sources; +2. Constraints; +3. Simulation Sources; +4. Utility Sources. + +В рамках текущего курса лабораторных работ мы будем взаимодействовать только с первыми тремя из них. + +Помните, что несмотря на использование слова "папка", речь идет не о директориях операционной системы. Папки проекта — это всего лишь удобная абстракция для управления иерархией проекта. + +В папке `Design Sources` строится иерархия проектируемых модулей (исходников цифровых схем, которые в будущем могут быть воспроизведены в ПЛИС или заказной микросхеме). + +Папка `Constraints` содержит файлы ограничений, помогающих реализовать проект на конкретной ПЛИС (см. ["Этапы реализации проекта в ПЛИС"](../Introduction/Implementation%20steps.md#implementation)). + +`Simulation Sources` хранит в себе иерархию верификационного окружения, **включая модули из папки** `Design Sources` — т.е. все модули (как синтезируемые, так и не синтезируемые), которые будут использованы при моделировании. + +> Обратите внимание на то, вкладка `Hierarchy` не содержит файлов. Здесь отображается иерархия модулей проекта. Один модуль может быть использован несколько раз — и в этом случае он будет столько же раз отображён в иерархии, хотя файл, хранящий описание этого модуля останется один (см. _рис. 6_). + +#### Добавление файла в проект + +Для того, чтобы добавить в проект новый файл, необходимо нажать на значок `+`, расположенный в верхней части окна `Sources` (либо использовать комбинацию горячих клавиш `Alt+A`). + +Появится окно добавления исходников. На первой странице этого окна будет необходимо выбрать тип добавляемого файла (см. _рис. 2_). + +- файлы ограничений для синтеза схемы под конкретную ПЛИС (`Constraints`); +- файлы проектируемой схемы (`Design Sources`); +- файлы верификационного окружения для верификации схемы (`Simulation Sources`). + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_02.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_02.png) + +_Рисунок 2. Первая страница окна добавления исходников._ + +В первую очередь мы хотим описать какую-нибудь простую схему, поэтому необходимо убедиться, что активным выбран пункт `Design Sources`. Выбрав, нажимаем `Next`. + +Появится страница, представленная на _рис. 3_, которая предлагает три варианта добавления исходников. + +1. добавить существующий файл; +2. добавить все имеющиеся файлы в заданной директории; +3. создать новый файл. + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_03.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_03.png) + +_Рисунок 3. Вторая страница окна добавления исходников._ + +Создадим новый файл, нажав на соответствующую кнопку окна. Появится всплывающее окно, предлагающее выбрать тип файла и его имя (см. _рис. 4_). В поле `File Type` выберите `SystemVerilog` (этот тип будет использоваться в качестве основного на протяжении всего курса кроме случаев, когда будет сказано иное). В поле `File Name` задайте имя новому файлу (в рамках примера, имя файла будет `max_min`). Указывать расширение файла не нужно — САПР автоматически его добавит в зависимости от выбранного типа файла. Когда всё будет готово, нажмите на `OK`. После того, как были добавлены (или созданы) все необходимые источники, можно нажать кнопку `Finish` в окне `Add Sources`. + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_04.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_04.png) + +_Рисунок 4. Окно создания нового файла._ + +В случае, если создавался новый файл, после нажатия на кнопку `Finish` появится окно, предлагающее автоматически создать прототип модуля, указав в графическом интерфейсе направление и разрядность его портов (см. _рис. 5_). В рамках данного примера, откажемся от данного предложения, нажав кнопки `Cancel->Yes`. + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_05.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_05.png) + +_Рисунок 5. Окно описания входов и выходов модуля._ + +После добавления файлов с исходными кодами, Vivado автоматически начнет обновлять иерархию проекта. Вы можете заметить это по появившейся надписи `Updating` с анимацией крутящейся стрелки, показанной на _рис. 6_. + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_06.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_06.png) + +_Рисунок 6. Уведомление об обновлении иерархии проекта._ + +Пока в окне есть данное уведомление, не рекомендуется запускать подпрограммы во `Flow Navigator` (к примеру, пытаться открыть схематик, запустить симуляцию/синтез и т.п.), поскольку иерархия проекта еще не построена и в конечном итоге может либо произойти ошибка, либо будет выполнено действие не для нужного вам модуля. + +> В зависимости от того, какие подпрограммы запущены через `Flow Navigator`, в момент вызова окна `Add Sources`, Vivado автоматически будет стараться выбрать наиболее подходящий пункт (что не всегда будет совпадать с вашим намереньем). К примеру, вы описали модуль, запустили симуляцию, чтобы его проверить, а затем решили описать следующий модуль. Из-за того, что в момент вызова окна `Add Sources` в фоне запущена симуляция, в этом окне по умолчанию будет выбран пункт `Simulation Sources`. + +После того, как Vivado закончит обновлять иерархию (и, если при создании файла вы отказались указывать порты модуля, нажав на кнопку `Cancel`), рядом с папкой `Design Sources` появится стрелка, позволяющая развернуть эту папку, внутри которой обнаружится подпапка `Non-module Files` с созданным нами файлом. Новый файл пометили таким образом, поскольку он не содержит модуля. Как только в нем окажется описание какого-нибудь модуля, эта подпапка пропадёт. + +Откроем редактор двойным кликом по файлу `max_min.sv` и опишем в нём код, приведённый в листинге 1. В коде _листингов 1-3_ могут содержаться логические ошибки — они запланированы и будут найдены и исправлены в главе "Руководство по поиску и исправлению ошибок". + +```Verilog +module max_min( + input logic [31:0] a, + input logic [31:0] b, + output logic [31:0] max, + output logic [ 3:0] min +); + + always_comb begin + if(a > b) begin + max = a; + min = b; + end + else begin + max = b; + min = b; + end + end + +endmodule +``` + +_Листинг 1. Описание модуля max\_min._ + +Не забудьте сохранить файл после описания в нем модуля нажав в редакторе на значок дискеты, или комбинацию клавиш `Ctrl+S`. + +Аналогичным образом, добавьте в `Design Sources` проекта файлы `half_divider` и `vector_abs` и опишите в них модули, представленные в _листингах 2-3_ соответственно (все файлы листингов находятся в репозитории в папке [Vivado Basics/vector_abs](./vector_abs/)). На второй странице окна добавления исходников, представленном на _рис. 3_, вы можете создавать сразу несколько новых файлов. При создании убедитесь, что вы выбрали корректный тип файла. + +```Verilog +module half_divider( + input logic [31:0] numerator, + output logic [31:0] quotient +); + + assign quotient = numerator << 1'b1; + +endmodule +``` + +_Листинг 2. Описание модуля half\_divider._ + +```Verilog +module vector_abs( + input logic [31:0] x, + input logic [31:0] y, + output logic [31:0] abs +); + + + logic [31:0] min; + logic [31:0] min_half; + + max_min max_min_unit( + .a(x), + .b(y), + .max(max), + .min(min) + ); + + half_divider div_unit( + .numerator(min), + .quotient(min_half) + ); + + assign abs = max + min_half; + +endmodule +``` + +_Листинг 3. Описание модуля vector\_abs._ + +В `Simulation Sources` добавьте файл tb_vector_abs, описываемый _листингом 4_. + +```Verilog +module tb_vector_abs(); + +logic [31:0] a; +logic [31:0] b; +logic [31:0] res; + +vector_abs dut( + .x(a), + .y(b), + .abs(res) +); +integer err_count = 0; + +task check_result(input logic [31:0]a, b, res); +begin : check_result + reg [31:0] ref_res; + ref_res = a < b? a/2 + b : a + b/2; + if (res !== ref_res) begin + $display("Incorrect res at time %0t:", $time); + $display("a = %0d, b = %0d", a, b); + $display("design res = %0d", res); + $display("reference res = %0d", ref_res); + $display("------------------"); + err_count = err_count + 1'b1; + end +end +endtask + +initial begin : test + integer i; + $timeformat(-9,0,"ns"); + a = 0; b = 0; + #5; + check_result(a,b,res); + + + a = 1; b = 1; + #5; + check_result(a,b,res); + + a = 3; b = 4; + #5; + check_result(a,b,res); + + + for(i = 0; i < 100; i=i+1) begin + a = $random()&32'hff; b = $random()&32'hff; + #5; + check_result(a,b,res); + end + + $display("Test has been finished with %d errors", err_count); + if(err_count == 0) begin + $display("SUCCESS!"); + end + $finish(); +end +endmodule +``` + +_Листинг 4. Описание модуля tb\_vector\_abs._ + +#### Построение иерархии модулей + +После создания указанных файлов и описания в них модулей из листингов 2-4, иерархия модулей примет следующий вид, представленный на _рис. 7_. + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_07.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_07.png) + +_Рисунок 7. Иерархия проекта, представленная в свёрнутом виде._ + +Нажав на стрелку слева от модуля `vector_abs`, иерархия развернётся (_рис. 8_). + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_08.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_08.png) + +_Рисунок 8. Иерархия проекта, представленная в развёрнутом виде._ + +Обратите внимание на то, что модуль `vector_abs` выделен жирным относительно других модулей. Такое выделение означает, что данный модуль выбран в качестве **модуля верхнего уровня** (**top-level module**). Это означает, это данный модуль и представляет итоговую схему, которую мы проектируем, и что другие подпрограммы во `Flow Navigator`, такие как `RTL ANALYSIS`, `SYNTHESIS`, `IMPLEMENTATION` и `PROGRAM AND DEBUG` будут обрабатывать именно этот модуль. Если вдруг вы захотите работать с другим модулем (например, с модулем, `half_divider`) — его необходимо пометить вручную в качестве модуля верхнего уровня. Для этого необходимо нажать по нему правой кнопкой мыши, и в выпадающем меню выбрать `Set as Top` (см. _рис. 9_). + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_09.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_09.png) + +_Рисунок 9. Выбор модуля верхнего уровня (показана середина выпадающего списка)._ + +Обратите внимание, как строится иерархия проекта. Модули, являющиеся объектами других модулей "вложены" в эти модули. Причем в иерархии проекта сперва указывается имя объекта модуля, затем через двоеточие имя самого модуля. В скобках указывается имя файла, где модуль описан. Модуль, который не содержится в других модулях не имеет имени объекта модуля (т.к. нет сущности, которая бы этот объект создавала). Если модуль будет содержать несколько объектов одного и того же модуля, в иерархии будут отображены все эти объекты — именно поэтому нужно понимать, чем иерархия модулей отличается от дерева файлов. Несмотря на то, что модуль описан всего в одном файле, в иерархии проекта может встречаться несколько экземпляров одного и того же модуля. + +Добавьте в `Simulation Sources` файл `tb_vector_abs`, содержимое которого представлено в _листинге 4_. + +```Verilog +module tb_vector_abs(); + +logic [31:0] a; +logic [31:0] b; +logic [31:0] res; + +vector_abs dut( + .x(a), + .y(b), + .abs(res) +); +integer err_count = 0; + +task check_result(input logic [31:0]a, b, res); +begin : check_result + reg [31:0] ref_res; + ref_res = a < b? a/2 + b : a + b/2; + if (res !== ref_res) begin + $display("Incorrect res at time %0t:", $time); + $display("a = %0d, b = %0d", a, b); + $display("design res = %0d", res); + $display("reference res = %0d", ref_res); + $display("------------------"); + err_count = err_count + 1'b1; + end +end +endtask + +initial begin : test + integer i; + $timeformat(-9,0,"ns"); + a = 0; b = 0; + #5; + check_result(a,b,res); + + + a = 1; b = 1; + #5; + check_result(a,b,res); + + a = 3; b = 4; + #5; + check_result(a,b,res); + + + for(i = 0; i < 100; i=i+1) begin + a = $random()&32'hff; b = $random()&32'hff; + #5; + check_result(a,b,res); + end + + $display("Test has been finished with %d errors", err_count); + if(err_count == 0) begin + $display("SUCCESS!"); + end + $finish(); +end +endmodule +``` + +_Листинг 4. Описание модуля tb\_vector\_abs._ + +#### Ошибки иерархии + +В случае, если при создании какого-либо из файлов вы ошиблись с папкой назначения (добавили файл, предназначенный для `Design Sources` в `Simulation Sources` или наоборот), вы можете перенести этот файл в нужную папку без необходимости его удаления и повторного добавления. Для этого кликните по нужному файлу правой кнопкой мыши и выберите `Move to Design/Simulation sources` (см. _рис. 10_). + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_10.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_10.png) + +_Рисунок 10. Перенос модуля в нужную папку._ + +После добавления модуля `tb_vector_abs`, обратите внимание на иерархию `Simulation Sources`. Обратите внимание на то, что все модули `Design Sources` продублированы в `Simulation Sources`. Это ещё одно отличие от дерева файлов. Физически каждый модуль находится всего в одном файле, здесь представлена иерархия модулей. + +Можно также заметить, что модуль верхнего уровня в `Simulation Sources` другой. Модуль верхнего уровня в `Simulation Sources` определяет то, какой модуль будет использоваться при симуляции (обычно это тестбенч, внутри которого создан объект проверяемого модуля). Модули верхнего уровня в `Design Sources` и `Simulation Sources` не связаны друг с другом (вам не нужно выбирать модулем верхнего уровня в `Design Sources` тот модуль, что вы будете проверять с помощью тестбенча в `Simulation Sources`). + +Давайте изменим в модуле `tb_vector_abs` название модуля `vector_abs`, использовавшееся при создании объекта `DUT` (например на `vector`). Получившаяся иерархия модулей представлена на _рис. 11_. + +![../.pic/Vivado%20Basics/03.%20Project%20manager/fig_11.png](../.pic/Vivado%20Basics/03.%20Project%20manager/fig_11.png) + +_Рисунок 11. Иерархия проекта с отсутствующим модулем._ + +Иерархия обновилась, но поскольку в проекте не существует модуля с названием `vector`, это отобразилось соответствующим образом. Поскольку модуль `vector_abs` не является частью модуля `tb_vector_abs`, он перестал быть вложенным модулем и разместился рядом в `Simulation Sources` (в `Design Sources` иерархия осталась прежде, т.к. изменения коснулись только модуля `tb_vector_abs`, расположенного в `Simulation Sources`). + +### Вкладка Libraries + +В данной вкладке находятся файлы проекта, сгруппированные по библиотекам. В рамках данного курса, эта вкладка использоваться не будет. + +### Вкладка Compile Order + +Обычно Vivado сам определяет порядок компиляции по иерархии проекта. Однако, в некоторых ситуациях он может определить что-то неправильно. На данной вкладке вы можете исправить порядок компиляции (скорее всего, вам может потребоваться эта вкладка, для указания порядка компиляции пакетов SystemVerilog). + +## Дополнительные материалы + +Более подробную информацию по окну `Sources` вы можете найти в руководстве пользователя Vivado: ["Vivado Design Suite User Guide: Using the Vivado IDE (UG893)"](https://docs.xilinx.com/r/en-US/ug893-vivado-ide) (раздел ["Using the Sources Window"](https://docs.xilinx.com/r/en-US/ug893-vivado-ide/Using-the-Sources-Window)). diff --git a/Vivado Basics/04. Simulation.md b/Vivado Basics/04. Simulation.md new file mode 100644 index 00000000..1b24946d --- /dev/null +++ b/Vivado Basics/04. Simulation.md @@ -0,0 +1,72 @@ +# Как запустить симуляцию в Vivado + +Симуляция — это один из видов моделирования. Моделирование используется для проверки поведения разработанного устройства. Для этого, на входы модуля подаются тестовые воздействия, а с его выходов считывается результат. Параллельно этому процессу, те же самые тестовые воздействия отправляются и в эталонную модель устройства. Результат модели сверяют с результатом проектируемого устройства и, в случае расхождения, сигнализируют об ошибке. + +Генерация тестовых воздействий, подача их на верифицируемое устройство и модель, сверка результатов и логирование ошибок — все это выполняется средствами верификационного окружения, которое в рамках данных лабораторных работ будет именоваться как "тестбенч". Тестбенчи — это несинтезируемые модули, поэтому они не должны находиться в папке `Design Sources`, вместо этого для них есть папка `Simulation Sources` (см. ["Менеджер проекта"](03.%20Project%20manager.md)). + +Для каждого верифицируемого модуля в репозитории есть отдельный тестбенч. Перед запуском моделирования, необходимо убедиться, что в качестве модуля верхнего уровня в папке `Simulation Sources` выбран тестбенч того модуля, который вы собираетесь верифицировать. + +Есть несколько способов запустить симуляцию, рассмотрим два из них: + +1. На панели слева, в разделе `SIMULATION`, нажать `Run Simulation` → `Run Behavioral Simulation`. + +![../.pic/Vivado%20Basics/04.%20Simulation/fig_01.png](../.pic/Vivado%20Basics/04.%20Simulation/fig_01.png) + +_Рисунок 1. Запуск симуляции через вкладку `SIMULATION` окна `Flow Navigator`._ + +1. В иерархии проекта нажать по папке `sim_1` правой кнопкой мыши, далее выбрать `Run Simulation` → `Run Behavioral Simulation`. + +![../.pic/Vivado%20Basics/04.%20Simulation/fig_02.png](../.pic/Vivado%20Basics/04.%20Simulation/fig_02.png) + +_Рисунок 2. Запуск симуляции через контекстное меню папки `sim_1` в `Simulation Sources`._ + +После запуска симуляции будет промоделировано определенное количество времени, задаваемое через настройки проекта (после создания проекта мы сделали это количество равное одной секунде), после чего моделирование приостанавливается. Моделирование может быть остановлено досрочно самим тестбенчем. + +## Окна для работы с симуляцией + +После запуска симуляции в основной части окна Vivado откроется окно симуляции, представленное на _рис. 3_. + +![../.pic/Vivado%20Basics/04.%20Simulation/fig_03.png](../.pic/Vivado%20Basics/04.%20Simulation/fig_03.png) + +_Рисунок 3. Окно симуляции._ + +Данное окно состоит из 4-х под-окон: + +1. окно с вкладками `Scope` и `Sources`; +2. окно с вкладками `Objects` и `Protocol Instances`; +3. окно редактора с открытыми файлами и появившимся там окном временной диаграммы (которая также представляет собой файл); +4. Окно c вкладками `Tcl Console`, `Messages` и `Log`. + +## Окно с вкладками Scope и Sources + +Вкладка Sources является той же самой вкладкой, что использовалась вами при добавлении и описании исходников и подробно разобрана в главе "[Менеджер проекта](./03.%20Project%20manager.md)". + +Вкладка Scope отображает область видимости симуляции, верхним уровнем в которой является модуль верхнего уровня `Simulation Sources` и библиотека `glbl`, которую в рамках данного курса можно будет игнорировать. Раскрыв модуль верхнего уровня, можно увидеть иерархию модулей подобную иерархии в `Simulation Sources`. Выбрав конкретный модуль во вкладке `Scope`, можно "отправить" его на временную диаграмму: либо перетащив его в область сигналов, либо нажав по нему правой кнопкой мыши, и выбрав `Add to Wave Window`. В этом случае, на временную диаграмму добавятся входы и выходы этого модуля, а также его внутренние сигналы. Кроме того, выбор модуля во вкладке `Scope` влияет на отображение содержимого окна с вкладкой `Objects`. + +На вкладке `Objects` находятся все объекты, связанные с модулем, выбранным во вкладке `Scope`: его входы и выходы, внутренние провода и регистры, параметры этого модуля и т.п. С помощью данной вкладки можно добавлять отдельные объекты выбранного модуля. + +## Панель инструментов симуляции + +После запуска симуляции, вверху окна Vivado меняется панель инструментов. На _рис. 3_ обозначены следующие кнопки: + +1. сбросить симуляцию (горячая клавиша `Ctrl+Shift+F5`); +2. запустить симуляцию до тех пор, пока она не будет остановлена тестбенчем или вручную (горячая клавиша `F3`); +3. запустить симуляцию указанного справа от кнопки промежутка времени (горячая клавиша `Shift+F2`); +4. перезапустить симуляцию (по умолчанию горячей клавиши нет, но может быть добавлена в настройках); +5. закрыть симуляцию. + +Отличие сброса симуляции от её перезапуска отличается в следующем. При сбросе симуляции очищаются промоделированные значения добавленных на временную диаграмму сигналов (сами сигналы остаются на месте), при этом время симуляции перемещается на нулевую отметку (т.е симуляция начнется заново). Подобное действие может быть необходимо в случае отладки, или же посреди моделирования вы добавили на временную диаграмму новые сигналы, и хотите увидеть их поведение с самого начала симуляции. При сбросе симуляции не выполняется компиляция исходников (даже если их содержимое было изменено). + +Перезапуск симуляции аналогичен закрытию симуляции и повторному её открытию. При этом, если в исходниках происходили изменения — файлы будут перекомпилированы. Обратите внимание, что Vivado в первую очередь обнаруживает только изменения, сделанные из собственного редактора. В случае, если файлы были изменены извне (в особенности это касается `mem`-файлов, которые начинают использоваться начиная с четвертой лабораторной работы) — Vivado может не обнаружить новых изменений. В случае, если симуляция ранее уже запускалась и с тех пор Vivado не обнаружил изменений в файлах — повторная компиляция, не производится и симуляция запускается средствами уже скомпилированных объектов. В случае, если изменения были сделаны извне, но Vivado их не обнаружил, можно очистить предыдущую сборку нажав правой кнопкой мыши по кнопки `Simulation` в окне `Flow Navigator` и выбрав `Reset Behavioral Simulation` (см. _рис. 4_). + +![../.pic/Vivado%20Basics/04.%20Simulation/fig_04.png](../.pic/Vivado%20Basics/04.%20Simulation/fig_04.png) + +_Рисунок 4. Сброс файлов симуляции._ + +Таким образом, в случае если вы добавили сигналы на временную диаграмму, и хотите увидеть их поведение с нулевого момента времени, или же вы хотите очистить лог сообщений и увидеть сообщения только до определенного момента (т.е. все действия, которые не связаны с повторной компиляцией исходных кодов), имеет смысл сбросить симуляцию и выполнить моделирование повторно. + +В случае, если вы изменили исходный код какого-то из модулей, и хотите выполнить моделирование обновленного кода, симуляцию можно закрыть и запустить повторно теми же способами, которыми вы запустили её в прошлый раз, либо перезапустить симуляцию в с помощью кнопки `4`, представленной на _рис. 3_. + +> Если вы изменили модуль верхнего уровня в `Simulation Sources`, вам необходимо закрыть текущую симуляцию. Без этого новая не сможет запуститься и будет выдавать ошибку "boost filesystem remove: Процесс не может получить доступ к файлу". Подробнее об этой ошибке рассказано в главе "Список типичных ошибок в Vivado". + +Подробнее о поиске ошибок и работе с временной диаграммой рассказано в главе "Руководство по поиску ошибок". diff --git a/Vivado Basics/Debug manual.md b/Vivado Basics/05. Bug hunting.md similarity index 75% rename from Vivado Basics/Debug manual.md rename to Vivado Basics/05. Bug hunting.md index 1420e297..e7ca68b9 100644 --- a/Vivado Basics/Debug manual.md +++ b/Vivado Basics/05. Bug hunting.md @@ -1,17 +1,18 @@ -# Руководство по поиску и исправлению ошибок в проекте +# Руководство по поиску функциональных ошибок ## Цель -При выполнении лабораторных работ вы непременно будете сталкиваться с множеством ошибок. И это нормально: **"Не ошибается тот, кто ничего не делает" — © Джейсон Стейтем**. -Важно воспитать в себе положительное восприятие обнаружения ошибок (ведь это приводит к улучшению вашего творения). Если относиться к обнаружению ошибок отрицательно, то вы подсознательно будете пытаться найти ошибки спустя рукава, но, если вы "в домике", и ошибок не видите — это не значит, что их нет. +При выполнении лабораторных работ вы непременно будете сталкиваться с множеством ошибок. И это нормально: **"Не ошибается тот, кто ничего не делает" — © Джейсон Стейтем**. -При должном отношении, поиск ошибок может превратиться в увлекательное детективное расследование, где у вас есть "место преступления" (обнаруженное несоответствие в поведении, обычно это не сама ошибка, а ее следствие, круги на воде) и какой-то "набор улик" (фрагменты лога, исходный код). И вы по чуть-чуть будете разматывать "нераспутываемую паутину лжи", получая все новые улики, ведущие к истинной ошибке. +Важно воспитать в себе положительное восприятие обнаружения ошибок (ведь это приводит к улучшению вашего творения). Если относиться к обнаружению ошибок отрицательно, то вы подсознательно будете пытаться найти ошибки спустя рукава, но, если вы "в домике", и ошибок не видите — это не значит, что их нет. -Этот документ посвящен практикуму по поискам подобных ошибок в **SystemVerilog**-коде. +При должном отношении, поиск ошибок может превратиться в увлекательное детективное расследование, где у вас есть "место преступления" (обнаруженное несоответствие в поведении, обычно это не сама ошибка, а ее следствие, круги на воде) и какой-то "набор улик" (фрагменты лога, исходный код). И вы, по чуть-чуть, будете разматывать "нераспутываемую паутину лжи", получая всё новые улики, ведущие к истинной ошибке. + +Этот документ представляет собой практикум по поиску подобных ошибок в **SystemVerilog**-коде. > Обратите внимание на то, как ставится ударение в словосочетании "временна́я диаграмма" (не "вре́менная"). В обиходе это словосочетание заменяется словом "времянка". -- [Руководство по поиску и исправлению ошибок в проекте](#руководство-по-поиску-и-исправлению-ошибок-в-проекте) +- [Руководство по поиску функциональных ошибок](#руководство-по-поиску-функциональных-ошибок) - [Цель](#цель) - [Алгоритм поиска ошибок](#алгоритм-поиска-ошибок) - [Работа с логом при появлении ошибок](#работа-с-логом-при-появлении-ошибок) @@ -27,78 +28,82 @@ ## Алгоритм поиска ошибок -1. Обычно всё начинается с сообщения в логе тестов (никто не проверяет глазами временную диаграмму сложных проектов, состоящую из тысяч сигналов, меняющихся миллионы раз за микросекунду), но на наших простых лабах, этот шаг иногда может быть и пропущен. -Сообщение в логе обычно содержит следующую ключевую информацию: имя сигнала, на котором установилось неверное значение, и время когда это произошло. Чем лучше написаны тесты, тем больше ключевой информации будет отражено в сообщении, поэтому написание тестов является своего рода искусством. -2. Получив имя сигнала и время, мы отправляемся на временную диаграмму и проверяем нашу ошибку. Как это сделать? Необходимо определить по коду, какие сигналы и каким образом управляют нашим сигналом. Вариантов может быть несколько: +1. Обычно всё начинается с сообщения в логе тестов (никто не проверяет глазами временную диаграмму сложных проектов, состоящую из тысяч сигналов, меняющихся миллионы раз за микросекунду), но на наших лабораторных работах с относительно простыми модулями, этот шаг иногда может быть и пропущен. +Сообщение в логе обычно содержит следующую ключевую информацию: имя сигнала, на котором установилось неверное значение, и время, когда это произошло. Чем лучше написано верификационное окружение, тем больше ключевой информации будет отражено в сообщении, поэтому его написание является своего рода искусством. +1. Получив имя сигнала и время, мы отправляемся на временную диаграмму и проверяем нашу ошибку. Как это сделать? Необходимо определить по коду, какие сигналы и каким образом управляют нашим сигналом. Вариантов может быть несколько: 1. Управляющие сигналы имеют корректное значение, но логика, по которой они управляют сигналом неверна, из-за этого на нем возникает неверное значение. Это идеальный случай, при возникновении которого мы сразу же находим причину проблемы и исправляем ее. - 2. Логика управления верна, а какая-то часть управляющих сигналов имеет неверное значение (пусть для примера, неверное значение будет на управляющем сигнале `X`). Это означает, что обнаруженное несоответствие сигналов является уже следствием какой-то ошибки, и мы должны вернуться к шагу 2, проверяя источники сигналов для сигнала `X`. Так происходит до тех пор, пока мы не попадаем в тип 1. + 2. Логика управления верна, а какая-то часть управляющих сигналов имеет неверное значение (пусть для примера, неверное значение будет на управляющем сигнале `X`). Это означает, что обнаруженное несоответствие сигналов является уже следствием какой-то ошибки, и мы должны вернуться к шагу 2, проверяя источники для сигнала со значением `X`. Так происходит до тех пор, пока мы не попадаем в тип 1. 3. Логика управления и значения управляющих сигналов верны. Это самый сложный тип ошибок, который заключается либо в ошибке в спецификации разрабатываемого устройства, либо в САПРе или компонентах, влияющих на его работу. В рамках данного курса вас не должны заботить данные ошибки, и при их возникновении вам стоит обратиться к преподавателю (предварительно убедившись, что ошибка совершенно точно не подходит под первые два варианта). 4. Любая возможная комбинация всех предыдущих типов. -3. Обнаружив первопричину ошибки, мы исправляем ее (возможно дополняя набор тестов, или внеся правки в спецификацию), и повторно запускаем все тесты, чтобы убедиться в двух вещах: +2. Обнаружив первопричину ошибки, мы исправляем ее (возможно дополняя набор тестов, или внеся правки в спецификацию), и повторно запускаем все тесты, чтобы убедиться в двух вещах: 1. ошибка действительно исправлена 2. исправление ошибки не породило новых ошибок -Давайте отработаем эти шаги на примере отладки ошибок в проекте по [вычислению приблизительной длины вектора](../Other//vector_abs/). +Давайте отработаем эти шаги на примере отладки ошибок в проекте по [вычислению приблизительной длины вектора](./vector_abs/), создание которого было описано в документе "[Менеджер проекта](./03.%20Project%20manager.md)". ## Работа с логом при появлении ошибок После запуска симуляции мы видим в логе множество ошибок: -![../.pic/Vivado%20Basics/Debug%20manual/fig_01.png](../.pic/Vivado%20Basics/Debug%20manual/fig_01.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_01.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_01.png) _Рисунок 1. Пример сообщения об ошибках в тесте._ В любой ситуации с множеством ошибок, сначала надо разбираться с самой первой из них, поскольку она может быть причиной появления всех остальных. Поэтому листаем лог до момента первой ошибки: -![../.pic/Vivado%20Basics/Debug%20manual/fig_02.png](../.pic/Vivado%20Basics/Debug%20manual/fig_02.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_02.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_02.png) _Рисунок 2. Пример конкретной ошибки в тесте._ -В логе сказано, что в момент времени `5ns`, на дизайн подавались координаты вектора, равные `0` и `0`, модель посчитала, что длина вектора равна нулю, в то время как дизайн вернул значение `x`. +В логе сказано, что в момент времени `5ns`, на вход схемы подавались координаты вектора, равные `0` и `0`, модель посчитала, что длина вектора равна нулю, в то время как схема вернула значение `x`. ## Поиск ошибки на временной диаграмме Давайте найдем это место на временной диаграмме. Обычно, сразу после запуска симуляции на временной диаграмме отображено место, где симуляция остановилась (возможно с очень неподходящим масштабом). Для начала подгоним масштаб таким образом, чтобы вся временная диаграмма умещалась в окне. Это делается либо нажатием правой кнопкой мыши по в области отображения сигналов, с выбором "Full View" во всплывающем меню, либо нажатием соответствующей кнопки на панели временной диаграммы (см. _рис. 4_), либо нажатием комбинации клавиш `Ctrl+0`. Затем найдем приблизительное место рядом с тем временем, что нас интересует, установим там курсор, и приблизим масштаб (покрутив колесиком мыши при зажатой клавише `Ctrl`), периодически уточняя местоположения курсора, пока не найдем интересующее нас место. -![../.pic/Vivado%20Basics/Debug%20manual/fig_03.png](../.pic/Vivado%20Basics/Debug%20manual/fig_03.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_03.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_03.png) _Рисунок 3. Пример временной диаграммы сразу поле остановки моделирования._ -![../.pic/Vivado%20Basics/Debug%20manual/fig_04.png](../.pic/Vivado%20Basics/Debug%20manual/fig_04.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_04.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_04.png) _Рисунок 4. Пример установки масштаба временной диаграммы таким образом, чтобы та помещалась в текущем окне._ -![../.pic/Vivado%20Basics/Debug%20manual/fig_05.png](../.pic/Vivado%20Basics/Debug%20manual/fig_05.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_05.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_05.png) _Рисунок 5. Пример временной диаграммы после подгонки масштаба._ -![../.pic/Vivado%20Basics/Debug%20manual/fig_06.png](../.pic/Vivado%20Basics/Debug%20manual/fig_06.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_06.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_06.png) _Рисунок 6. Установка курсора в начало моделирования, чтобы, при увеличении масштаба, временная диаграмма сходилась к началу._ -![../.pic/Vivado%20Basics/Debug%20manual/fig_07.png](../.pic/Vivado%20Basics/Debug%20manual/fig_07.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_07.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_07.png) _Рисунок 7. Временная диаграмма, отмасштабированная к времени ошибки с рис. 2._ -Мы видим ровно ту информацию, которую нам предоставил тестбенч. Теперь надо разобраться в причинах возникновения X-состояния. Такое может произойти в двух ситуациях: какой-то из сигналов, формирующих этот находится в `X` или `Z` состоянии, либо же два каких-то сигнала одновременно пытаются выставить разные значения (подобный вариант встречается куда реже и в цикле ваших лабораторных вряд ли встретится). +Мы видим ровно ту информацию, которую нам предоставил тестбенч. Теперь надо разобраться в причинах возникновения X-состояния. Такое может произойти по множеству причин, вот три из них: + +1. какой-то из сигналов, формирующих этот находится в `X` или `Z` состоянии; +2. два каких-то сигнала одновременно пытаются выставить разные значения на целевой сигнал; +3. этот сигнал является выходом модуля, но был описан с ключевым словом `input`. ## Открытие файла исходного кода проблемного сигнала -В любом случае, первым делом необходимо определить, источник формирования значения сигнала `res`. Для этого, откроем файл с исходным кодом, где определен данный сигнал. Для этого, нажмем правой кнопкой мыши по имени сигнала на временной диаграмме, и выберем `Go To Source Code`: +В любом случае, первым делом необходимо определить, источник формирования значения сигнала `res`. Откроем файл с исходным кодом, где определен данный сигнал. Для этого, нажмем правой кнопкой мыши по имени сигнала на временной диаграмме, и выберем `Go To Source Code`: -![../.pic/Vivado%20Basics/Debug%20manual/fig_08.png](../.pic/Vivado%20Basics/Debug%20manual/fig_08.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_08.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_08.png) _Рисунок 8. Переход к месту объявления "проблемного" сигнала._ -Открывается следующий код (с курсором на строчке `wire [31:0] res;`): +Откроется код, представленный в _листинге 1_ (с курсором на строчке `logic [31:0] res;`): -```SystemVerilog -module tb(); +```Verilog +module tb_vector_abs(); -reg [31:0] a; -reg [31:0] b; -wire [31:0] res; +logic [31:0] a; +logic [31:0] b; +logic [31:0] res; vector_abs dut( .x(a), @@ -108,9 +113,11 @@ vector_abs dut( //... ``` -Выделив `res` мы видим, что у нас подсветился `res` в строке `abs(res)`. Это означает, что мы завели наш провод внутрь объекта `dut` модуля `vector_abs`, и у нас проблема второго типа (X-состояние передалось от выхода `abs` модуля `vector_abs` проводу `res` модуля `tb`). +_Листинг 1. Начало кода симулируемого тестбенча._ -В этом можно убедиться, если вытащить сигналы модуля `vector_abs` на временную диаграмму. Чтобы это сделать, надо переключиться на окно `Scope`, где размещена иерархия объектов нашего тестбенча +Выделив `res` мы видим, что у нас подсветился `res` в строке `abs(res)`. Это означает, что мы завели наш провод внутрь объекта `dut` модуля `vector_abs`, и у нас проблема второго типа (в логике работы провода `res` нет ошибок, он принял некорректное значение, поскольку ему таковое передали). + +В этом можно убедиться, если вытащить сигналы модуля `vector_abs` на временную диаграмму. Чтобы это сделать, надо переключиться на окно `Scope`, где размещена иерархия моделируемых объектов. ## Добавление сигналов объектов на временную диаграмму @@ -118,20 +125,20 @@ vector_abs dut( Выделим объект `dut`. В окне `Objects` справа отобразятся все внутренние сигналы (входы/выходы, внутренние провода и регистры) объекта `dut`: -![../.pic/Vivado%20Basics/Debug%20manual/fig_09.png](../.pic/Vivado%20Basics/Debug%20manual/fig_09.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_09.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_09.png) _Рисунок 9. Отображение внутренних сигналов проверяемого модуля._ -Вообще говоря, мы уже видим, что выход `abs` (к которому подключен наш провод `res`) находится в X-состоянии, но для отработки навыков, разберемся с добавлением на временную диаграмму. Можно поступить двумя способами: +Вообще говоря, мы уже видим, что выход `abs` (к которому подключен наш провод `res`) находится в X-состоянии, но для отработки навыков, разберемся с добавлением новых сигналов на временную диаграмму. Можно поступить двумя способами: 1. Добавить все сигналы (то, что видно в окне `Objects` на временную диаграмму) из окна `Scope` для этого, либо перетаскиваем нужный нам объект, зажав левую кнопку мыши на временную диаграмму, либо жмем правой кнопкой мыши по нужному объекту, и выбираем `Add to Wave Window` 2. Добавить отдельные сигналы из окна `Objects`. Для этого выделяем их (возможно множественное выделение через модификаторы `shift` или `ctrl`), и как и в прошлом случае, либо перетаскиваем сигналы левой кнопкой мыши, либо добавляем их через правую кнопку мыши. -![../.pic/Vivado%20Basics/Debug%20manual/fig_10.png](../.pic/Vivado%20Basics/Debug%20manual/fig_10.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_10.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_10.png) _Рисунок 10. Добавление сигналов модуля на временную диаграмму._ -![../.pic/Vivado%20Basics/Debug%20manual/fig_11.png](../.pic/Vivado%20Basics/Debug%20manual/fig_11.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_11.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_11.png) _Рисунок 11. Результат добавления сигналов модуля на временную диаграмму._ @@ -139,22 +146,22 @@ _Рисунок 11. Результат добавления сигналов м Для того чтобы объединить сигналы в группу, необходимо их выделить. Это можно сделать двумя способами: -1. "прокликав" интересующие сигналы при зажатой клавише `Ctrl`; +1. кликнув левой кнопкой мыши по каждому из интересующих сигналов при зажатой клавише `Ctrl`; 2. если речь идет о диапазоне сигналов, можно выбрать сигнал с одного края, после чего, при зажатой клавише `Shift`, выбрать сигнал с другого края этого диапазона. После выбора, необходимо нажать правой кнопкой мыши по выделенным сигналам, и в низу выпадающего списка выбрать `New Group`. -![../.pic/Vivado%20Basics/Debug%20manual/fig_12.png](../.pic/Vivado%20Basics/Debug%20manual/fig_12.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_12.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_12.png) _Рисунок 12. Пример создания группы сигналов (контекстное меню было обрезано для удобства отображения)._ После создания группы, ей нужно будет дать имя. В случае, если все сигналы принадлежат одному модулю, удобно называть группу сигналов именем этого модуля. -![../.pic/Vivado%20Basics/Debug%20manual/fig_13.png](../.pic/Vivado%20Basics/Debug%20manual/fig_13.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_13.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_13.png) _Рисунок 13. Пример созданной группы сигналов._ -Данну группу можно сворачивать и разворачивать, нажимая на соответствующую стрелку слева от имени группы. +Данную группу можно сворачивать и разворачивать, нажимая на соответствующую стрелку слева от имени группы. > Обратите внимание, что часть сигналов отображают какое-то значение (сигнал `abs` отображает X-состояние), а часть не отображают ничего. Так произошло, потому что провод `abs` **непрерывно связан** с проводом `res`, с точки зрения симулятора это одна сущность, и записывая во время моделирования значения для сигнала `res`, симулятор неявно записывал значения для сигнала `abs`, чего не скажешь про остальные сигналы, которых не было во время моделирования на временной диаграмме. @@ -164,7 +171,7 @@ _Рисунок 13. Пример созданной группы сигнало Для этого, необходимо на панели симуляции нажать кнопку `Restart` (`|◀`), а затем кнопку `Run all` (`▶`) или `Run for` (`▶t`). Положение кнопок в окне Vivado иллюстрирует _рис. 14_. -![../.pic/Vivado%20Basics/Debug%20manual/fig_14.png](../.pic/Vivado%20Basics/Debug%20manual/fig_14.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_14.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_14.png) _Рисунок 14. Расположение кнопок, управляющих моделированием в окне Vivado._ @@ -179,52 +186,53 @@ _Рисунок 14. Расположение кнопок, управляющи `Run all` отличается от `Run for` тем, что в качестве количества моделируемого времени указывается "бесконечность", и моделирование будет остановлено только вручную, либо вызовом соответствующей инструкции. -> Обратите внимание, что для добавления недостающих значений добавленных сигналов лучше всего выполнять описанную выше инструкцию. Аналогичного результата можно добиться и нажатием на кнопку `Relaunch Simulation`, однако эта команда запускает повторную компиляцию и запуск симуляции, что для крупных проектов выльется в потерю времени на излишнюю компиляцию. +> Обратите внимание, что для добавления недостающих значений добавленных сигналов лучше всего выполнять описанную выше инструкцию. Аналогичного результата можно добиться и нажатием на кнопку `Relaunch Simulation`, однако эта команда работает дольше и, если вы не меняли исходный код модулей, не нужна. Кроме того, чтобы курсор и лог снова не ушли далеко от места первой ошибки, можно сразу указать, необходимое нам время моделирования перед выполнением команды `Run for`: `5ns`. -![../.pic/Vivado%20Basics/Debug%20manual/fig_15.png](../.pic/Vivado%20Basics/Debug%20manual/fig_15.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_15.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_15.png) _Рисунок 15. Пример моделирования 5ns._ На _рис. 16_ представлен результат моделирования с новыми сигналами. -![../.pic/Vivado%20Basics/Debug%20manual/fig_16.png](../.pic/Vivado%20Basics/Debug%20manual/fig_16.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_16.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_16.png) _Рисунок 16. Результат повторного моделирования после добавления на временную диаграмму новых сигналов._ -Видим два сигнала в Z-состоянии и один сигнал в X-состоянии. Обычно, сигналы с Z-состоянием проще всего исправить, т.к. зачастую это забытое или некорректное подключение провода. Кроме того, сигнал, зависящий от сигнала с Z-состоянием, может оказаться в X-состоянии, так что это может быть решением нашей проблемы, поэтому займемся проводами `min` и `min_half`. Сперва займемся сигналом `min` и перейдем к шагу 2 нашего алгоритма (нажимаем правой кнопкой мыши и выбираем `Go To Source Code`): +Видим два сигнала в Z-состоянии и один сигнал в X-состоянии. Обычно, сигналы с Z-состоянием проще всего исправить, т.к. зачастую это забытое или некорректное подключение провода. Кроме того, сигнал, зависящий от сигнала с Z-состоянием, может оказаться в X-состоянии, так что это может быть решением нашей проблемы, поэтому проверим провода `min` и `min_half`. Сперва займемся сигналом `min` и перейдем к шагу 2 нашего алгоритма (нажимаем правой кнопкой мыши и выбираем `Go To Source Code`): -```SystemVerilog - module vector_abs( - input [31:0] x, - input [31:0] y, - output[31:0] abs - ); +```Verilog +module vector_abs( + input logic [31:0] x, + input logic [31:0] y, + output logic [31:0] abs +); - wire [31:0] min; - wire [31:0] min_half; + logic [31:0] min; + logic [31:0] min_half; - max_min max_min_unit( - .a(x), - .b(y), - .max(max), - .min(min) - ); + max_min max_min_unit( + .a(x), + .b(y), + .max(max), + .min(min) + ); +//... ``` ## Исправление сигналов с Z-состоянием Мы видим, что сигнал `min` подключен к выходу `min` объекта `max_min_unit` модуля `max_min`. Добавим сигналы этого модуля на временную диаграмму. Для этого, необходимо раскрыть список объектов, содержащихся в объекте `dut` иерархии объектов `Scope` и выбрать там объект `max_min_unit`. -![../.pic/Vivado%20Basics/Debug%20manual/fig_17.png](../.pic/Vivado%20Basics/Debug%20manual/fig_17.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_17.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_17.png) _Рисунок 17. Добавление сигналов вложенных модулей на временную диаграмму._ Добавляем внутренние сигналы на временную диаграмму, группируем их под именем `max_min`, и повторяем моделирование. -![../.pic/Vivado%20Basics/Debug%20manual/fig_18.png](../.pic/Vivado%20Basics/Debug%20manual/fig_18.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_18.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_18.png) _Рисунок 18. Результат добавления и группировки сигналов подмодуля `max_min`._ @@ -234,39 +242,39 @@ _Рисунок 18. Результат добавления и группиро Если присмотреться к этим двум сигналам еще пристальней, то можно увидеть, что у сигнала `min` объекта `dut` разрядность 32 бита, в то время как разрядность сигнала `min` объекта `max_min_unit` составляет 4 бита. -Это и является проблемой: мы подключили 4 бита сигнала 4-разрядного сигнала `min` к младшим 4 битам 32-разрядного сигнала `min`, а остальные разряды остались не подключенными. +Это и является проблемой: мы подключили 4 бита 4-разрядного сигнала `min` к младшим 4 битам 32-разрядного сигнала `min`, а остальные разряды остались не подключенными. По всей видимости, при написании модуля `max_min`, была указана неверная разрядность сигнала `min`, вместо `31` было написано `3`. Исправим это и повторим моделирование. > Обратите внимание, что поскольку мы изменили исходный код, в этот раз необходимо нажать на кнопку `Relaunch Simulation`, поскольку нужна повторная компиляция проекта. -![../.pic/Vivado%20Basics/Debug%20manual/fig_19.png](../.pic/Vivado%20Basics/Debug%20manual/fig_19.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_19.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_19.png) _Рисунок 19. Результат моделирования после исправления разрядности сигнала `min`._ -В логе сообщается о 102 найденных ошибках. Ровно на одну ошибку меньше, чем было ранее. Это не означает, что в проекте осталось 102 ошибки, только то, что, исправив данную ошибку — мы действительно что-то исправили, и один из тестовых сценариев, который ранее завершался ошибкой, теперь завершился без нее. +В логе сообщается о 102 найденных ошибках. Ровно на одну ошибку меньше, чем было ранее. Это не означает, что в проекте осталось 102 ошибки, только то, что, исправив данную ошибку — мы действительно что-то исправили, и один из тестовых сценариев, который ранее завершался ошибкой, теперь завершился без неё. -Помните, что если в проекте много ошибок, то часть ошибок может выправлять поведение других ошибок (хоть и не всегда, но иногда минус на минус может выдать плюс контексте ошибок проекта), поэтому надо осторожно полагаться на число найденных ошибок, если их больше нуля. +Помните, что если в проекте много ошибок, то часть ошибок может выправлять поведение других ошибок (хоть и не всегда, но иногда минус на минус может выдать плюс контексте ошибок проекта), поэтому надо осторожно полагаться на число найденных ошибок, если это число больше нуля. Посмотрим на нашу временную диаграмму снова, и выберем дальнейшие действия: -![../.pic/Vivado%20Basics/Debug%20manual/fig_20.png](../.pic/Vivado%20Basics/Debug%20manual/fig_20.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_20.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_20.png) _Рисунок 20. Временная диаграмма после исправления разрядности сигнала `min`._ Мы видим, что на временной диаграмме не осталось сигналов в X или Z-состоянии, а значит мы собрали все "низковисящие" улики нашего с вами расследования. Вернемся к месту преступления и попробуем поискать новые улики: -![../.pic/Vivado%20Basics/Debug%20manual/fig_21.png](../.pic/Vivado%20Basics/Debug%20manual/fig_21.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_21.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_21.png) _Рисунок 21. Первая ошибка в новом логе моделирования._ ## Поиск ошибки в сигналах, формирующих проблемный сигнал -Мы видим, что первой ошибкой в логе стала не та ошибка, что была прежде. Раньше первый неверный результат мы видели в момент времени `5ns`, когда на дизайн подавались значения `0` и `0`, теперь же первой ошибкой стал момент времени `10ns`, когда на дизайн подаются значения `1` и `1`. Наше устройство считает, что результат должен равняться `3`, в то время как модель считает, что результат должен равняться `1`. Проверим, нет ли ошибки в модели и посчитаем результат самостоятельно: +Мы видим, что первой ошибкой в логе стала не та ошибка, что была прежде. Раньше первый неверный результат мы видели в момент времени `5ns`, когда на схему подавались значения `0` и `0`, теперь же первой ошибкой стал момент времени `10ns`, когда на схему подаются значения `1` и `1`. Наше устройство считает, что результат должен равняться `3`, в то время как модель считает, что результат должен равняться `1`. Проверим, нет ли ошибки в модели и посчитаем результат самостоятельно: -Для определения приблизительной длины вектора в евклидовом пространстве(вычисления квадратного корня из суммы квадратов / длины гипотенузы прямоугольного треугольника) можно воспользоваться формулой: +Для определения приблизительной длины вектора в евклидовом пространстве (т.е. длины гипотенузы прямоугольного треугольника, которая равна квадратному корню из суммы квадратов катетов) можно воспользоваться формулой: -`sqrt(a^2 + b^2) ≈ max + min/2`, где `max` и `min` — большее и меньшее из пары чисел соответственно [**Ричард Лайонс: Цифровая обработка сигналов, Глава 13.2, стр. 475**]. +`sqrt(a^2 + b^2) ≈ max + min/2`, где `max` и `min` — большее и меньшее из пары чисел соответственно [**Ричард Лайонс: Цифровая обработка сигналов, стр. 475**]. Подставим наши числа в формулу (поскольку оба числа равны, не важно какое из них будет максимумом, а какое минимумом): @@ -274,7 +282,7 @@ _Рисунок 21. Первая ошибка в новом логе модел 1 + 1/2 = 1.5 ``` -Ни модель, ни дизайн не правы? +Ни модель, ни схема не правы? На самом деле, наше устройство поддерживает только целочисленную арифметику, поэтому результат будет: @@ -284,26 +292,27 @@ _Рисунок 21. Первая ошибка в новом логе модел Модель правильно отразила особенность нашего устройства и дала корректный результат. -Значит надо смотреть как формируется результат в нашем устройстве, посмотрим на выход `abs` в модуле `vector_abs`: +Значит надо смотреть как формируется результат в нашем устройстве. Посмотрим на выход `abs` в модуле `vector_abs`: -```SystemVerilog +```Verilog assign abs = max + min_half; ``` Выход `abs` зависит от двух внутренних сигналов: max и `min_half`. В соответствии с нашим алгоритмом, либо проблема в логике, связывающей эти два сигнала (операции сложения), либо в значении какого-то из этих сигналов, либо комбинации этих вариантов. -Изучив модуль, мы понимаем, что в логике этого присваивания проблем нет, т.к. оно повторяет логику формулы `max + min/2`, складывая максимум с половиной минимума. Значит проблема в значении какого-то из этих сигналов (или обоих из них). Посчитаем значения этих сигналов самостоятельно (для сложного проекта эти значения бы посчитала модель): +Изучив модуль, мы понимаем, что в логике этого присваивания проблем нет, т.к. оно повторяет логику формулы `max + min/2`, складывая максимум с половиной минимума. Значит проблема в значении какого-то из этих сигналов (или обоих из них). Посчитаем значения этих сигналов самостоятельно (для сложного проекта эти значения посчитала бы модель): + `1` и `0`. Смотрим, какие значения установлены на сигналах `max` и `min_half` в момент времени `10ns`. -![../.pic/Vivado%20Basics/Debug%20manual/fig_22.png](../.pic/Vivado%20Basics/Debug%20manual/fig_22.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_22.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_22.png) -_Рисунок 22. Значения сигналов `max` и `min_half` в момент времени `10 ns` +_Рисунок 22. Значения сигналов `max` и `min_half` в момент времени `10 ns` (интересующие нас сигналы выделены зелёным)_ -> Обратите внимание: вы можете менять и цвета сигналов временной диаграммы через контекстное меню выделенных сигналов. +> Обратите внимание: вы можете менять цвета сигналов временной диаграммы через контекстное меню выделенных сигналов. -Мы видим, что в момент времени `10 ns` значения `max` и `min_half` изменились ак `1 -> 4` и `2 -> 8` соответственно. Нас интересуют значения `1` и `2`, т.к. в момент времени `10ns` на выходе дизайна в этот момент был установившийся результат для предыдущих значений (еще не успел посчитаться результат для новых значений). +Мы видим, что в момент времени `10 ns` значения `max` и `min_half` изменились как `1 -> 4` и `2 -> 8` соответственно. Нас интересуют значения `1` и `2`, т.к. в момент времени `10ns` на выходе схемы в этот момент был установившийся результат для предыдущих значений (еще не успел посчитаться результат для новых значений). Значение `max=1` совпадает с ожидаемым, в то время как `min_half=2` явно нет. @@ -311,7 +320,7 @@ _Рисунок 22. Значения сигналов `max` и `min_half` в м Как и с сигналом `abs`, необходимо определить сигналы, влияющие на значение сигнала `min_half`. Данный сигнал подключен к выходу `quotient` модуля `half_divider`, поэтому мы будем смотреть исходный код данного модуля: -```SystemVerilog +```Verilog module half_divider( input [31:0] numerator, output[31:0] quotient @@ -326,11 +335,11 @@ endmodule Выход данного модуля зависит от входа `numerator` и логики сдвига влево на 1. Это значит, что проблема либо в логике, либо в значении, подаваемом на вход. Выведем сигнал `numerator` на временную диаграмму и посмотрим на его значение в момент времени `10ns. -![../.pic/Vivado%20Basics/Debug%20manual/fig_23.png](../.pic/Vivado%20Basics/Debug%20manual/fig_23.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_23.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_23.png) _Рисунок 23. Значение сигнала `numerator` в момент времени `10 ns`._ -Мы помним, что в момент, когда дизайн начал выдавать неправильный результат, на его входы подавались числа `1` и `1`, это значит, что на вход `numerator` пришло корректное значение: минимум из этих двух чисел и правда равен `1`. Проверим логику данного модуля. +Мы помним, что в момент, когда схема начала выдавать неправильный результат, на его входы подавались числа `1` и `1`, это значит, что на вход `numerator` пришло корректное значение: минимум из этих двух чисел и правда равен `1`. Проверим логику данного модуля. ## Исправление логики проблемного сигнала @@ -344,19 +353,19 @@ _Рисунок 23. Значение сигнала `numerator` в момент Повторяем моделирование. -![../.pic/Vivado%20Basics/Debug%20manual/fig_24.png](../.pic/Vivado%20Basics/Debug%20manual/fig_24.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_24.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_24.png) _Рисунок 24. Результат моделирования после исправления оператора сдвига._ Снова на одну ошибку меньше. Не унываем, вряд ли в проекте число ошибок больше, чем число непустых строк самого проекта. Возвращаемся к начальной ошибке: -![../.pic/Vivado%20Basics/Debug%20manual/fig_25.png](../.pic/Vivado%20Basics/Debug%20manual/fig_25.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_25.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_25.png) _Рисунок 25. Первая ошибка в повторном моделировании._ Мы продвинулись во времени безошибочного моделирования до `15 ns`, начинаем наше расследование с начала: -На вход дизайна подаются значения `3` и `4`, дизайн считает, что результатом вычисления `max + min/2` будет `2`, модель считает, что `5`. Посчитаем сами: +На вход схемы подаются значения `3` и `4`, схема считает, что результатом вычисления `max + min/2` будет `2`, модель считает, что `5`. Посчитаем сами: ```text max=4 @@ -368,31 +377,31 @@ max + min/2 = 4 + 3/2 = 4 + 1 = 5 ## Проблема необъявленных сигналов -К этому моменту на вашей временной диаграмме скорей всего стало уже очень много сигналов. Уберем лишние, оставив только внутренние сигналы модуля `vector_abs` (для этого выделяем не нужные сигналы, и удаляем их с помощью клавиши `Delete`). +К этому моменту на вашей временной диаграмме скорей всего стало уже очень много сигналов. Уберем лишние, оставив только внутренние сигналы модуля `vector_abs` (для этого выделяем ненужные сигналы, и удаляем их с помощью клавиши `Delete`). -![../.pic/Vivado%20Basics/Debug%20manual/fig_26.png](../.pic/Vivado%20Basics/Debug%20manual/fig_26.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_26.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_26.png) _Рисунок 26. Поведение внутренних сигналов модуля `vector_abs` на временной диаграмме._ В глаза сразу же бросается, что сигнал `max` внешне отличается от всех остальных — он ведет себя как однобитный сигнал. Если все остальные сигналы 32-разрядные, то и сигнал `max` должен быть таким же. Перейдем к объявлению этого сигнала, чтобы это исправить (нажав правой кнопкой мыши, и выбрав `Go To Source Code`): -```SystemVerilog - module vector_abs( - input [31:0] x, - input [31:0] y, - output[31:0] abs - ); +```Verilog +module vector_abs( + input logic [31:0] x, + input logic [31:0] y, + output logic [31:0] abs +); - wire [31:0] min; - wire [31:0] min_half; + logic [31:0] min; + logic [31:0] min_half; - max_min max_min_unit( - .a(x), - .b(y), - .max(max), - .min(min) - ); + max_min max_min_unit( + .a(x), + .b(y), + .max(max), + .min(min) + ); //... ``` @@ -400,7 +409,7 @@ _Рисунок 26. Поведение внутренних сигналов м Для исправления этой ошибки, объявим сигнал `max` с корректной разрядностью и повторим моделирование. -![../.pic/Vivado%20Basics/Debug%20manual/fig_27.png](../.pic/Vivado%20Basics/Debug%20manual/fig_27.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_27.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_27.png) _Рисунок 27. Результат моделирования после объявления пропущенного сигнала._ @@ -408,13 +417,13 @@ _Рисунок 27. Результат моделирования после о Число ошибок сократилось до 40! Мы явно на верном пути. Повторяем предыдущие шаги, вернувшись к первой ошибке: -![../.pic/Vivado%20Basics/Debug%20manual/fig_28.png](../.pic/Vivado%20Basics/Debug%20manual/fig_28.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_28.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_28.png) _Рисунок 28. Первая ошибка в повторном моделировании._ -В этот раз первая ошибка осталась прежней, только теперь дизайн считает, что результат должен равняться шести (в прошлый раз дизайн выдавал `2`). Мы уже убедились, что в этом случае модель дает правильный результат, поэтому сразу перейдем к формирующим результат сигналам: +В этот раз первая ошибка осталась прежней, только теперь схема считает, что результат должен равняться шести (в прошлый раз схема выдавала `2`). Мы уже убедились, что в этом случае модель дает правильный результат, поэтому сразу перейдем к формирующим результат сигналам: -![../.pic/Vivado%20Basics/Debug%20manual/fig_29.png](../.pic/Vivado%20Basics/Debug%20manual/fig_29.png) +![../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_29.png](../.pic/Vivado%20Basics/05.%20Bug%20hunting/fig_29.png) _Рисунок 29. Поведение внутренних сигналов модуля `vector_abs` на временной диаграмме._ @@ -422,4 +431,4 @@ _Рисунок 29. Поведение внутренних сигналов м Не отходя далеко от кассы, мы замечаем, что значение `min`, формирующее сигнал `min_half` неверно: его значение `4`, а должно быть `3`. -Используя [файлы исходного кода проекта](../Other/vector_abs/), попробуйте разобраться в последней обнаруженной нами ошибке. +Используя [файлы исходного кода проекта](./vector_abs/), попробуйте разобраться в последней обнаруженной нами ошибке. diff --git a/Vivado Basics/06. RTL Analysis.md b/Vivado Basics/06. RTL Analysis.md new file mode 100644 index 00000000..3b1452b9 --- /dev/null +++ b/Vivado Basics/06. RTL Analysis.md @@ -0,0 +1,37 @@ +# Анализ RTL + +**RTL** (**register transfer level** — **уровень межрегистровых передач**) — это один из уровней абстракции при проектировании цифровой схемы, когда та описывается в виде регистров и логики передачи данных между этими регистрами. + +Vivado предоставляет средства по анализу RTL-кода, позволяя обнаруживать и исправлять ошибки на раннем этапе, до выполнения моделирования и попытки синтезировать проект. Для того чтобы провести анализ, необходимо выполнить предобработку проекта (`Open Elaborated Design`, см. _рис. 1_). + +![../.pic/Vivado%20Basics/06.%20RTL%20Analysis/fig_1.png](../.pic/Vivado%20Basics/06.%20RTL%20Analysis/fig_1.png) + +_Рисунок 1. Инструменты анализа RTL в окне `Flow Navigator`._ + +Итогом предобработки станет отображение графической схемы (подробнее рассказано в документе "[Этапы реализации проекта в ПЛИС](../Introduction/Implementation%20steps.md)"). Если схема не отобразилось, можно нажать на кнопку `Schematic`. + +![../.pic/Vivado%20Basics/06.%20RTL%20Analysis/fig_2.png](../.pic/Vivado%20Basics/06.%20RTL%20Analysis/fig_2.png) + +_Рисунок 2. Пример построения схемы для схемы, описанной в документе "[Менеджер проекта](./03.%20Project%20manager.md)"._ + +Допустим нашли ошибку, изменили код модуля и хотите увидеть обновленную схему. Вы нажимаете на кнопку `Schematic` у вас появляется новая вкладка, но схема на ней осталась без изменений. Дело в том, что открытие новой схемы требует повторной предобработки проекта. Для этого необходимо либо закрыть окно `Elaborated Design`, и открыть его заново, либо нажать на кнопку `Reload Design` вверху окна Vivado, которая появляется в информационном сообщении при обновлении кода модуля (см. _рис. 3_). + +![../.pic/Vivado%20Basics/06.%20RTL%20Analysis/fig_3.png](../.pic/Vivado%20Basics/06.%20RTL%20Analysis/fig_3.png) + +_Рисунок 3. Информационное сообщение о том, что предобработанный проект устарел в виду изменения исходников. Кнопка Reload позволяет выполнить повторную предобработку для обновленного кода._ + +Помимо построения схемы, Vivado выполнит её анализ, а обнаруженные проблемы будут отображены во вкладке `Messages`, которая расположена внизу окна Vivado (_рис. 4_). + +![../.pic/Vivado%20Basics/06.%20RTL%20Analysis/fig_4.png](../.pic/Vivado%20Basics/06.%20RTL%20Analysis/fig_4.png) + +_Рисунок 4. Окно с сообщениями о результатах выполненных операциях. Для удобства отображения, информационные сообщения скрыты, оставлены только предупреждения._ + +Проблема окна сообщений заключается в том, что их число быстро накапливается и превращается в огромный поток, с которым тяжело работать даже с включенными фильтрами. Более того, сообщения сохраняются между запусками анализа, т.е. даже если вы исправите какую-то проблему — сообщение о ней так и останется до тех пор, пока вы не очистите окно сообщений. + +Начиная с версии 2023.1 в Vivado появился специальный инструмент — линтер, который анализирует код и сообщает о проблемах в отдельном окне. Проблемы группируются по типам и список проблем очищается и генерируется повторно каждый раз, когда запускается линтер. + +Если вы уже прочли документ "[Руководство по поиску функциональных ошибок](./05.%20Bug%20hunting.md)", вы можете заметить, что предупреждения, которые Vivado вывел в окно сообщений напрямую связаны с ошибками, которые мы обнаружили в процессе симуляции. Разница заключается в том, что Vivado вывел сообщения об этих ошибках практически мгновенно, в то время как нам для этого потребовалось проводить целое расследование. Именно в этом и заключается мощь данного инструмента — он позволяет найти большинство простых ошибок, давая возможность сосредоточиться на более сложных. + +## Дополнительные материалы + +Подробнее о взаимодействии с окном схемы можно прочитать в руководстве пользователя Vivado: ["Vivado Design Suite User Guide: Using the Vivado IDE (UG893)"](https://docs.xilinx.com/r/en-US/ug893-vivado-ide) (раздел ["Using the Schematic Window"](https://docs.xilinx.com/r/en-US/ug893-vivado-ide/Using-the-Schematic-Window)). diff --git a/Vivado Basics/07. Program and debug.md b/Vivado Basics/07. Program and debug.md new file mode 100644 index 00000000..18bc8fba --- /dev/null +++ b/Vivado Basics/07. Program and debug.md @@ -0,0 +1,31 @@ +# Как прошить ПЛИС + +После того как вы описали и верифицировали модуль, остается запрототипировать его в ПЛИС. Для этого в большинстве папок лабораторных работ есть подпапка `board_files` в которой хранятся необходимые файлы. Обычно там будет находиться модуль верхнего уровня и файл ограничений, которые позволяют связать вашу логику с периферией, расположенной на плате `Nexys-A7`. + +Для сборки итогового проекта вам необходимо: + +1. Добавить модуль верхнего уровня (содержащийся в файле с расширением `.sv`) в `Design Sources` вашего проекта. +2. Выбрать добавленный модуль в качестве модуля верхнего уровня вашего проекта. + 1. Для этого нажмите по нему правой кнопкой мыши. + 2. В контекстном меню выберете `Set as Top`. +3. Добавить файл ограничений (с расширением `.xdc`) в `Constraints` вашего проекта. Если такой файл уже есть в вашем проекте (а он будет в нём уже после первой лабораторной), вам необходимо заменить содержимое старого файла содержимым нового. Ограничения меняются от лабораторной к лабораторной. + +После выполнения указанных шагов, ваш проект готов к генерации битстрима — двоичного файла, с помощью которого реконфигурируется ПЛИС. + +По сути, весь процесс генерации битстрима и конфигурациии оным ПЛИС сводится к последовательному нажатию следующих четырех кнопок в группе `PROGRAM AND DEUBG` окна `Flow Navigator`, которые представлены на _рис. 1_. + +![../.pic/Vivado%20Basics/07.%20Program%20and%20debug/fig_1.png](../.pic/Vivado%20Basics/07.%20Program%20and%20debug/fig_1.png) + +_Рисунок 1. Порядок выполнения действий для компиляции проекта и прошивки ПЛИС._ + +Нажатие на кнопку Generate Bitstream позволяет сгенерировать двоичный код для конфигурации ПЛИС. В случае, если перед этим не были выполнены этапы синтеза и имплементации, появятся всплывающие окна, предлагающие выполнить эти этапы. Вам достаточно утвердительно отвечать во всех всплывающих окнах (варианты `YES`/`OK`, в зависимости от состояния вашего проекта, число появляющихся окон будет различным). Последним окном, информирующим о том, что двоичный файл готов будет `Bitstream Generation Completed` (в случае, если все этапы были выполнены без ошибок). + +Остаётся прошить ПЛИС. Для этого подключите отладочный стенд к USB-порту компьютера и включите на стенде питание. + +Затем запустите менеджер аппаратуры Vivado. Для этого нажмите на кнопку `Open Hardware Manager` (кнопка 2 на _рис. 1_). + +После, необходимо подключиться к ПЛИС. Для этого необходимо нажать на кнопку `Open Target` (кнопка 3 на _рис. 1_) и в контекстном меню выбрать вариант `Auto Connect`. + +И последним шагом остается прошить ПЛИС нажатием на кнопку `Program Device` (кнопка 4 на _рис. 1_). Появится всплывающее окно, предлагающее выбрать двоичный файл конфигурации, поле которого будет автоматически заполнено путем к последнему сгенерированному файлу. Вам не нужно ничего менять, только нажать на кнопку `Program`. + +После этого появится окно с индикатором реконфигурации ПЛИС. Когда окно закроется, ПЛИС будет сконфигурирована под прототип вашего модуля. diff --git a/Vivado Basics/08. Code processing errors.md b/Vivado Basics/08. Code processing errors.md new file mode 100644 index 00000000..1974ab8f --- /dev/null +++ b/Vivado Basics/08. Code processing errors.md @@ -0,0 +1,27 @@ +# Руководство по работе с ошибками обработки кода + +Некоторые ошибки (например ошибки синтаксиса или иерархии) могут привести к тому, что САПР не сможет построить схему или запустить симуляцию. + +Без должного опыта, при подобных ошибках можно растеряться, т.к. всплывающие окна, сообщающие об этих ошибках малоинформативны (см. _рис. 1-2_). + +Предположим, мы забыли поставить точку с запятой в конце одного из присваиваний, и попробовали запустить моделирование. + +В результате, всплывающие окна, представленные на _рис. 1-2_. + +![../.pic/Vivado%20Basics/08.%20Code%20processing%20errors/fig_01.png](../.pic/Vivado%20Basics/08.%20Code%20processing%20errors/fig_01.png) + +_Рисунок 1. Первое всплывающее окно при попытке запустить моделирование проекта с синтаксической ошибкой._ + +![../.pic/Vivado%20Basics/08.%20Code%20processing%20errors/fig_02.png](../.pic/Vivado%20Basics/08.%20Code%20processing%20errors/fig_02.png) + +_Рисунок 2. Второе всплывающее окно при попытке запустить моделирование проекта с синтаксической ошибкой._ + +Во втором окне есть кнопка `Open Messages View`. Нажмём её. Будет активировано окно сообщений, представленное на _рис. 3_. + +![../.pic/Vivado%20Basics/08.%20Code%20processing%20errors/fig_03.png](../.pic/Vivado%20Basics/08.%20Code%20processing%20errors/fig_03.png) + +_Рисунок 3. Окно сообщений после неудачной попытки запуска симуляции._ + +Сообщения из раздела `Vivado commands` на _рис. 2_ дают мало информации. Однако здесь же есть критические предупреждения о синтаксической ошибке с возможностью перейти к строчке в файле, вызвавшей это предупреждение. Разумеется, не всегда САПР может сообщить доступным языком в чем именно ошибка, в данном случае, он просто обнаружил что ключевое слово `end` встретилось не там, где оно должно было бы быть (оно встретилось до завершения оператора присваивания, который должен был быть завершен символом `;`). В этом случае, вам необходимо самим разобраться в чем именно заключается ошибка (для этого вы можете кликнуть по гиперссылке в критическом предупреждении — откроется редактор с местом ошибки). + +Помните, что большая часть сообщений в данном окне сохраняется даже если ошибка будет исправлена, поэтому рекомендуется очищать окно сообщений, в случае если появились ошибки и уже сложно понять какие из них старые, а какие из них новые. Сделать это можно, нажав на иконку корзины в окне сообщений. При этом удалятся не все ошибки, а только те, которые были вызваны процессами, запущенными пользователем. К примеру, если очистить окно сообщений, не исправив указанную ошибку, пропадут только ошибки из раздела `Vivado commands`. Дело в том, что критические предупреждения появились не после того, как мы попытались запустить моделирования, а после того, как Vivado автоматически запустил инструменты анализа кода. Делает он это автоматически каждый раз, когда сохраняется файл. Эти ошибки пропадут только когда повторный анализ покажет, что они были исправлены. diff --git a/Vivado Basics/Elaboration failed.md b/Vivado Basics/Elaboration failed.md deleted file mode 100644 index f7b18b18..00000000 --- a/Vivado Basics/Elaboration failed.md +++ /dev/null @@ -1,34 +0,0 @@ -# Инструкция по работе с ошибками элаборации - -Итак, вы описали модуль на языке SystemVerilog и хотите открыть логическую схему или запустить симуляцию, чтобы убедиться, что описание верно. - -Однако, в результате какого-то из этих действий появляется окно с сообщением о непонятной ошибке: - -![../.pic/Vivado%20Basics/Elaboration%20failed/simFail.png](../.pic/Vivado%20Basics/Elaboration%20failed/simFail.png) - -_Рисунок 1. Пример окна ошибки при попытке запустить моделирование._ - -Ничего страшного — ошибки, это часть учебного и рабочего процесса. - -Смело нажимаем `OK`, не читая сообщения (и поступаем так же, с еще одним открывшимся окном). - -Мы собираемся получить информацию об ошибки из более подробного источника: вкладки `Tcl Console`. - -![../.pic/Vivado%20Basics/Elaboration%20failed/err_log.png](../.pic/Vivado%20Basics/Elaboration%20failed/err_log.png) - -_Рисунок 2. Элементы `Tcl Console`, содержащей лог сообщений нашего взаимодействия с Vivado`._ - -На _рис. 2_ представлено: - -1. Место последнего запуска (конец синего текста) — лог хранит информацию по всем попыткам запуска моделирования / открытия схемы. Каждая такая попытка будет начинаться с текста синего цвета, а значит по нему можно отслеживать начало нашего лога. -2. Если попыток запуска было много, навигация по логу может быть осложнена. Для упрощения навигации, можно сворачивать неинтересующие нас попытки через кнопку, обозначенную на _рис. 2_ меткой `2`. -3. Кроме того, вместо сворачивания ненужных логов, можно очистить от сообщений всю `Tcl Console`, чтобы начать работу "с чистого листа". Это можно сделать нажав на кнопку с иконкой корзины, обозначенной меткой `3`. -4. Чтение ошибок в логе должно начинаться с самой первой, т.к. одна ошибка может причиной последующих. -5. Найдя первую ошибку, необходимо внимательно ознакомиться с сообщением этой ошибки (обозначено меткой `5`). -6. Сообщение об ошибке обычно сопровождается номером строки в конкретном файле, вызвавшей эту ошибку. - -Чаще всего этого сообщения будет достаточно, чтобы понять в чем дело. - -В случае, если вы все ещё не понимаете в чем проблема, сверьтесь со [списком типовых ошибок](../Other/FAQ.md). - -Если не помог и он, обратитесь к преподавателю. diff --git a/Vivado Basics/How to open a schematic.md b/Vivado Basics/How to open a schematic.md deleted file mode 100644 index 468116a6..00000000 --- a/Vivado Basics/How to open a schematic.md +++ /dev/null @@ -1,43 +0,0 @@ -# Как открыть цифровую схему проекта - -Одним из способов первичной оценки результатов описания модуля является просмотр логической схемы, построенной по описанию этого модуля. Порядок открытия схемы следующий: - -Сохраняем модуль → Слева на панели управления раскрываем вкладку `RTL ANALYSIS` → Раскрываем вкладку `Open Elaborated Design` → Нажимаем на `Schematic`. - -![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_1.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_1.png) - -_Рисунок 1. Расположение кнопки `Schematic`._ - -Нажатие на `Schematic` приведет к появлению окна `Elaborate Design`, в котором необходимо будет нажать на кнопку `OK`. - -![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_2.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_2.png) - -_Рисунок 2. Окно `Elaborate Design`._ - -После нажатия на `OK`, появится окно `Open Elaborated Design`, которое автоматически пропадет по завершению процесса. В случае если вы компилируете крупный проект и хотите продолжить работу во время компиляции, вы можете нажать на кнопку `Background`. - -![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_3.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_3.png) - -_Рисунок 3. Окно `Open Elaborated Design`._ - -После этого в окне `Project Manager` появится вкладка `Schematic`, где вы должны увидеть свою схему: - -![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_4.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_4.png) - -_Рисунок 4. Открывшаяся схема модуля._ - -> Обратите внимание, что во вкладках SYNTHESIS и IMPLEMENTATION также есть возможность открыть Schematic. Запуск в них строит схему на основе примитивов ПЛИС (см. "Этапы реализации проекта в ПЛИС"). В рамках лабораторных работ нам будет интересна именно цифровая схема, собранная из логических элементов, которая открывается при нажатии на `Schematic` во вкладке RTL ANALYSIS.` - -## Как обновить схему после правок модуля - -После правок в модуле, необходимо отобразить обновленную схему. Повторное нажатие на `Schematic` приведет лишь к открытию ещё одной вкладки со старой версией схемы. - -Однако, после изменения модуля вы можете обратить внимание на появление светло-жёлтого уведомления вверху окна `Project Manager`, где будет сказано о том, что построенный проект устарел, т.к. исходники были изменены, а рядом с ним — кнопку "Reload" (см. _рис. 5_). Нажатие по этой кнопке приведет к рекомпиляции проекта и открытию обновленной схемы. - -![../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_5.png](../.pic/Vivado%20Basics/How%20to%20open%20a%20schematic/fig_5.png) - -_Рисунок 5. Кнопка повторной загрузки схемы._ - -## Дополнительные материалы - -Подробнее о взаимодействии с окном схемы можно прочитать в руководстве пользователя Vivado: ["Vivado Design Suite User Guide: Using the Vivado IDE (UG893)"](https://docs.xilinx.com/r/en-US/ug893-vivado-ide) (раздел ["Using the Schematic Window"](https://docs.xilinx.com/r/en-US/ug893-vivado-ide/Using-the-Schematic-Window)). diff --git a/Vivado Basics/How to program an fpga board.md b/Vivado Basics/How to program an fpga board.md deleted file mode 100644 index 7bc1a29d..00000000 --- a/Vivado Basics/How to program an fpga board.md +++ /dev/null @@ -1,32 +0,0 @@ -# Как прошить ПЛИС - -После того как вы описали и верифицировали модуль, остается запрототипировать его в ПЛИС. Для этого в большинстве папок лабораторных работ есть подпапка `board_files` в которой хранятся необходимые для этого файлы. Обычно там будет находиться модуль верхнего уровня и файл ограничений, которые позволяют связать вашу логику с периферией, расположенной на плате `Nexys-A7`. - -Для сборки итогового проекта вам необходимо: - -1. Добавить модуль верхнего уровня (содержащийся в файле с расширением `.sv`) в `Design Sources` вашего проекта. -2. Выберете добавленный модуль в качестве модуля верхнего уровня вашего проекта. - 1. Для этого нажмите по нему правой кнопкой мыши. - 2. В контекстном меню выберете `Set as Top`. -3. Добавьте файл ограничений (с расширением `.xdc`) в `Constraints` вашего проекта. Если такой файл уже есть в вашем проекте (а он будет в нем уже после первой лабораторной), вам необходимо заменить старого файла содержимым нового. Ограничения меняются от лабы к лабе. - -После выполнения указанных шагов, ваш проект готов к генерации битстрима — двоичного файла, с помощью которого реконфигурируется ПЛИС. - -Для генерации битстрима вам необходимо нажать на `Generate Bitstream` во вкладке `PROGRAM AND DEBUG` окна `Flow Navigator` (левый нижний угол окна программы). - -![../.pic/Vivado%20Basics/How%20to%20program%20an%20fpga%20board/fig_1.png](../.pic/Vivado%20Basics/How%20to%20program%20an%20fpga%20board/fig_1.png) - -_Рисунок 1. Расположение кнопки `Generate Bitstream`._ - -После нажатия на эту кнопку, нажимайте утвердительно во всех всплывающих окнах (варианты `YES`/`OK`, в зависимости от состояния вашего проекта, число появляющихся окон будет различным). После успешной генерации битстрима откроется окно `Bitstream Generation Completed`. - -Остается прошить ПЛИС. Для этого подключите отладочный стенд к USB-порту компьютера и включите на стенде питание. Затем откройте окно `HARDWARE MANAGER` для этого: - -1. Убедитесь, что выбран пункт `Open Hardware Manager` в окне `Bitstream` и нажмите на OK. -2. Кликните `Open target` → `Auto Connect` → `Program device` → `Program`. - -![../.pic/Vivado%20Basics/How%20to%20program%20an%20fpga%20board/fig_2.png](../.pic/Vivado%20Basics/How%20to%20program%20an%20fpga%20board/fig_2.png) - -_Рисунок 2. Последовательность действий для прошивки ПЛИС._ - -После этого появится окно с индикатором реконфигурации ПЛИС. Когда окно закроется, в ПЛИС окажется прототип вашего модуля. diff --git a/Vivado Basics/How to use Source Window.md b/Vivado Basics/How to use Source Window.md deleted file mode 100644 index 5915cf73..00000000 --- a/Vivado Basics/How to use Source Window.md +++ /dev/null @@ -1,146 +0,0 @@ -# Окно исходников проекта Vivado - -Данный документ расскажет вам об одном из основных окон программы Vivado: `Sources`. Данное оно расположено в левом верхнем углу. Если вы его не видите, данное окно можно активировать через меню: `Window –> Sources`. Окно состоит из следующих вкладок: - -1. Hierarchy; -2. Libraries; -3. Compile Order. - -В определенных ситуациях в данном окне может появиться и вкладка `IP Cores`, но в рамках данного курса она нас не интересует. - -Рассмотрим первые три вкладки. - -## Иерархия модулей проекта - -Рассмотрим _рис. 1_. - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_01.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_01.png) - -_Рисунок 1. Окно `Sources`, открытое на вкладке `Hierarchy`._ - -Данная вкладка состоит из четырех "папок": - -1. Design Sources; -2. Constraints; -3. Simulation Sources; -4. Utility Sources. - -В рамках текущего курса лабораторных работ мы будем взаимодействовать только с тремя из них. - -Помните, что несмотря на использование слова "папка", речь идет не о директориях операционной системы. Папки проекта — это всего лишь удобная абстракция для управления иерархией проекта. - -В папке `Design Sources` строится иерархия проектируемых модулей (реальных схем, которые в будущем могут быть воспроизведены в ПЛИС или заказной микросхеме). - -Папка `Constraints` содержит файлы ограничений, помогающих реализовать проект на конкретной ПЛИС (см. ["Этапы реализации проекта в ПЛИС"](../Introduction/Implementation%20steps.md#implementation)). - -`Simulation Sources` хранит в себе иерархию верификационного окружения, включая модули из папки `Design Sources` — т.е. все модули (как синтезируемые, так и не синтезируемые), которые будут использованы пр моделировании. - -> Обратите внимание на то, вкладка `Hierarchy` не содержит файлов. Здесь отображается иерархия модулей проекта. Один модуль может быть использован несколько раз — и в этом случае он будет столько же раз отображён в иерархии, хотя файл, хранящий описание этого модуля останется один (см. _рис. 5_). - -Допустим, мы создали модуль полного однобитного сумматора `fulladder`, а также создали модуль полного четырехбитного сумматора `fulladder4`, содержимое которого мы только планируем описать, подключив внутри него четыре однобитных сумматора. - -Раскрыв папку `Design Sources` мы увидим два модуля – `fulladder` и `fulladder4`, которые пока что никак друг с другом не связаны. Двойное нажатие на название модуля приведёт к открытию файла, содержащего описание этого модуля. - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_02.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_02.png) - -_Рисунок 2. Содержимое папки `Design Sources`._ - -Модуль `fulladder4` является модулем верхнего уровня (top-level module). Это значит, что при попытке запуска моделирования или синтеза, Vivado будет работать именно с этим модулем. Чтобы сменить модуль верхнего уровня, необходимо нажать правой кнопкой мыши на интересующий модуль и выбрать `Set a top`. - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_03.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_03.png) - -_Рисунок 3. Пример смены модуля верхнего уровня._ - -Опишем логику работы четырехбитного сумматора таким образом, чтобы тот содержал четыре однобитных сумматора. После сохранения окно изменится так: - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_04.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_04.png) - -_Рисунок 4. Обновленное содержимое папки `Design Sources`._ - -После раскрытия ветки `fulladder4` будет отображено 4 подключенных модуля `fulladder`. - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_05.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_05.png) - -_Рисунок 5. Иерархия проекта с четырьмя копиями модуля `fulladder`._ - -В `Simulation Sources` мы видим один файл тестбенча, к которому что-то подключено, и модуль `fulladder4` с подключенными к нему другими модулями: - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_06.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_06.png) - -_Рисунок 6. Иерархия модулей `Simulation Sources`._ - -Модули из `Design Sources` автоматически попадают в `Simulation Sources`, так как эти модули используются при моделировании. - -Помните, что здесь отображается иерархия модулей. В реальности модуль `fulladder` описан всего один раз. - -Каждый раз, когда вы меняете что-то в модулях разрабатываемого устройства, это отражается как во вкладке `Design Sources`, так и в `Simulation Sources`. Раскроем вкладку с модулем `tb`: - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_07.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_07.png) - -_Рисунок 7. Пример иерархии с отсутствующим модулем._ - -Такая картина говорит нам о попытке подключить модуль, которого нет в проекте. Часто это связано с неправильным указанием подключаемого модуля. В данном случае мы хотим подключить модуль `half_adder` и Vivado не может его найти. - -```SystemVerilog -module tb(); - -//... - -half_adder DUT( - .A (a), - .B (b), - .P (p), - .S (s) -); - -// ... - -endmodule -``` - -Переименуем название подключаемого модуля на `fulladder4` и сохраним. - -```SystemVerilog -module tb(); - -//... - -fulladder4 DUT( - .A (a), - .B (b), - .P (p), - .S (s) -); - -// ... - -endmodule -``` - -После обновления в окне `Sources` модуль `fulladder4` "спрячется" под `tb`. Если раскрыть вкладку, будет видно, что `fulladder4` подключен к `tb`, а четыре модуля `fulladder` – к `fulladder4`. Также отметим, что `tb` является модулем верхнего уровня, значит, если мы захотим запустить симуляцию, то Vivado выполнит симуляцию именно для модуля `tb`. Изменить модуль верхнего уровня можно так же, как было описано ранее. - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_08.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_08.png) - -_Рисунок 8. Пример исправленной иерархии верификационного окружения._ - -После каждого сохранения файла проекта, иерархия проекта будет какое-то время обновляться. Это можно заметить по надписи `Updating` вверху окна (см. _рис 9_). Во время обновления иерархии не стоит выполнять операции синтеза или моделирования — это приведет к ошибке. - -![../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_09.png](../.pic/Vivado%20Basics/How%20to%20use%20Source%20Window/fig_09.png) - -_Рисунок 9. Окно `Sources` во время обновления иерархии проекта._ - -Одной из частой ошибок студентов бывает прикрепление файла не к той папке. Например, создание модуля проекта в папке `Simulation Sources` (из-за чего тот не появится в папке `Design Sources`), или создание модуля верификационного окружения в папке `Design Sources` (он же наоборот — окажется и в папке `Simulation Sources`, но при этом в папке Design Sources окажется несинтезируемый модуль, который может оказаться еще и модулем верхнего уровня, что приведет к ошибке). - -В случае, если произошла такая ошибка, она может быть легко исправлена нажатием правой кнопкой мыши по неправильно расположеному модулю и выбору кнопки: "Move to Design[или Simulation] sources". - -## Библиотеки проекта - -В данной вкладке находятся файлы проекта, сгруппированные по библиотекам. Обычно данная вкладка практически не используется. - -## Порядок компиляции сущностей проекта - -Обычно Vivado сам определяет порядок компиляции по иерархии проекта. Однако, в некоторых ситуациях он может определить что-то неправильно. На данной вкладке вы можете исправить порядок компиляции (скорее всего, вам может потребоваться эта вкладка, для указания порядка компиляции пакетов SystemVerilog). - -## Дополнительные материалы - -Более подробную информацию по окну `Sources` вы можете найти в руководстве пользователя Vivado: ["Vivado Design Suite User Guide: Using the Vivado IDE (UG893)"](https://docs.xilinx.com/r/en-US/ug893-vivado-ide) (раздел ["Using the Sources Window"](https://docs.xilinx.com/r/en-US/ug893-vivado-ide/Using-the-Sources-Window)). diff --git a/Vivado Basics/README.md b/Vivado Basics/README.md index 76ef9c6d..1d799f90 100644 --- a/Vivado Basics/README.md +++ b/Vivado Basics/README.md @@ -6,12 +6,13 @@ Дабы сократить порог вхождения в освоение этого инструмента, был написан ряд материалов по описанию базовых сценариев использования, который представлен в данной папке. -Здесь находятся инструкции о том как: +Здесь находятся следующие документы: -1. [создать демо-проект под отладочный стенд Nexys-7](Vivado%20trainer.md); -2. [прошить ПЛИС](How%20to%20program%20an%20fpga%20board.md); -3. [взаимодействовать с окном исходников проекта Vivado](How%20to%20use%20Source%20Window.md); -4. [открыть логическую схему написанного вами модуля](How%20to%20open%20a%20schematic.md); -5. [запустить симуляцию](Run%20Simulation.md); -6. [разобраться с ошибками, при попытке открыть схему / запустить симуляцию](Elaboration%20failed.md); -7. [находить и исправлять ошибки дизайна, найденные тестовым окружением](Debug%20manual.md). +1. [Создание нового проекта под отладочный стенд Nexys A7](./01.%20New%20project.md); +2. [Навигатор по маршруту проектирования](./02.%20Flow%20Navigator.md); +3. [Менеджер проекта](./03.%20Project%20manager.md); +4. [Как запустить симуляцию в Vivado](./04.%20Simulation.md); +5. [Руководство по поиску функциональных ошибок](./05.%20Bug%20hunting.md); +6. [Анализ RTL](./06.%20RTL%20Analysis.md); +7. [Как прошить ПЛИС](./07.%20Program%20and%20debug.md); +8. [Руководство по работе с ошибками обработки кода](08.%20Code%20processing%20errors.md) diff --git a/Vivado Basics/Run Simulation.md b/Vivado Basics/Run Simulation.md deleted file mode 100644 index 1965667c..00000000 --- a/Vivado Basics/Run Simulation.md +++ /dev/null @@ -1,35 +0,0 @@ -# Как запустить симуляцию в Vivado - -Симуляция — это один из видов моделирования. Моделирование используется чтобы проверки поведения разработанного устройства. Для этого, на входные сигналы подаются тестовые воздействия, а с выходных считывается результат. Параллельно этому процессу, те же самые тестовые воздействия отправляются и в эталонную модель устройства. Результат модели сверяют с результатом проектируемого устройства и, в случае расхождения, сигнализируют об ошибке. - -Генерация тестовых воздействий, подача их на верифицируемое устройство и модель, сверка результатов и логирование ошибок — все это выполняется средствами верификационного окружения, которое в рамках данных лабораторных работ будет именоваться как "тестбенч". Тестбенчи — это несинтезируемые модули, поэтому они не должны находиться в папке `Design Sources`, вместо этого для них есть папка `Simulation Sources` (см. ["Окно исходников проекта Vivado"](./How%20to%20use%20Source%20Window.md)). - -Для каждого верифицируемого модуля в репозитории есть отдельный тестбенч. Перед запуском моделирования, необходимо убедиться, что в качестве модуля верхнего уровня в папке `Simulation Sources` выбран тестбенч того модуля, который вы собираетесь верифицировать. - -Есть несколько способов запустить симуляцию, рассмотрим два из них: - -1. На панели слева, в разделе `SIMULATION`, нажать `Run Simulation` → `Run Behavioral Simulation`. - -![../.pic/Vivado%20Basics/Run%20Simulation/fig_1.png](../.pic/Vivado%20Basics/Run%20Simulation/fig_1.png) - -_Рисунок 1. Запуск симуляции через вкладку `SIMULATION` окна `Flow Navigator`._ - -2. В иерархии проекта нажать по папке `sim_1` правой кнопкой мыши, далее выбрать `Run Simulation` → `Run Behavioral Simulation`. - -![../.pic/Vivado%20Basics/Run%20Simulation/fig_2.png](../.pic/Vivado%20Basics/Run%20Simulation/fig_2.png) - -_Рисунок 2. Запуск симуляции через контекстное меню папки `sim_1` в `Simulation Sources`._ - -После запуска симуляции будет отмоделировано определенное количество времени, задаваемое через настройки проекта, после чего моделирование приостанавливается. Если этого времени не хватает для прохождения всех этапов симуляции (если в `Tcl Console` вы не видите сообщение о завершении моделирования), необходимо нажать кнопку `▶` (`Run all`) на панели инструментов появившегося окна `SIMULATION` (либо нажать горячую клавишу `F3`). - -В случае, если вы изменили исходный код какого-то из модулей, симуляцию можно перезапустить тем же способом, которым вы запустили её в первый раз, либо нажав кнопку `Relaunch Simulation`. - -В случае, не меняли исходный код, но хотите промоделировать модуль заново, вы можете воспользоваться кнопкой `Restart` (`|◀`). В этом случае, вы избежите повторной компиляции модулей. - -Для закрытия симуляции вы можете кликнуть на крестик окна `SIMULATION` (бирюзовое), либо нажать правой кнопкой мыши по `SIMULATION` и выбрать `Close Simulation`. - -![../.pic/Vivado%20Basics/Run%20Simulation/fig_3.png](../.pic/Vivado%20Basics/Run%20Simulation/fig_3.png) - -_Рисунок 3. Закрытие симуляции через окно `Flow Navigator`._ - -> Если вы изменили модуль верхнего уровня в `Simulation Sources`, вам необходимо закрыть текущую симуляцию. Без этого новая не сможет запуститься и будет выдавать ошибку "boost filesystem remove: Процесс не может получить доступ к файлу". diff --git a/Vivado Basics/Vivado trainer.md b/Vivado Basics/Vivado trainer.md deleted file mode 100644 index 3d359643..00000000 --- a/Vivado Basics/Vivado trainer.md +++ /dev/null @@ -1,99 +0,0 @@ -# Создание базового проекта с прошивкой ПЛИС в Vivado - -## Создание проекта в Системе Автоматизированного Проектирования (САПР) - -1. Запустить Vivado -2. Нажать `Create Project` -3. В открывшемся окне нажать Next -4. Ввести название проекта (никаких пробелов и кириллических символов) → Выбрать папку для проектов (создать каталок на D:/) → Поставить галку `Create project subdirectory` → Нажать `Next` -5. Выбрать RTL Project → Поставить галку `Do not specify sources at this time` → Нажать Next -6. Выставить фильтры, для сужения списка ПЛИС: - * Family: `Artix 7` - * Package: `CSG324`, - * Speed: `-1`. - -
- Скриншот окна с выставленными фильтрами - - ![../.pic/Vivado%20Basics/Vivado%20trainer/fig_01.png](../.pic/Vivado%20Basics/Vivado%20trainer/fig_01.png) - -
- -1. В списке выбрать ПЛИС `xc7a100tcsg324-1` → Нажать Next -2. Нажать Finish -3. Закрыть Vivado -4. Удалить папку -5. Повторить все действия самостоятельно - -## Создание модуля на SystemVerilog - -1. Создать новый SystemVerilog файл, для этого в окне `Sources` нажать на кнопку `+` -2. В открывшемся окне выбрать `Add or create design source` → Нажать `Next` -3. Нажать `Create File` → В открывшемся окне ввести имя модуля `top` и выбрать тип файла SystemVerilog → Нажать `OK` → В оставшемся окне нажать `Finish` -4. В открывшемся окне НЕ вводить названия портов и сразу нажать OK → После чего подтвердить выбор `Yes` -5. Двойным кликов в окне `Source` открыть файл `top.sv` -6. Написать следующий код: - -```SystemVerilog -module top ( - input logic clk, - input logic a, - input logic b, - output logic q -); - -logic c; - -assign c = a ^ b; - -always_ff @(posedge clk) begin - q <= c; -end - -endmodule -``` - -7. Сохранить изменения -8. Нажать `Open Elaborated Design` -9. Нажать `Schematic` в открывшемся списке -10. Проанализировать полученный результат (сопоставить с SystemVerilog-описанием) -11. Закрыть проект - -## Реализация простого проекта на отладочном стенде - -1. Создать новый проект -2. Создать новый SystemVerilog файл с названием `basic` -3. Написать следующий код: - -```SystemVerilog -module basic ( - input logic [15:0] SW, - output logic [15:0] LED -); - -assign LED[0] = SW[0] & SW[1]; -assign LED[2] = SW[2] | SW[3]; -assign LED[4] = SW[4] ^ SW[5]; -assign LED[10:6] = ~SW[10:6]; -assign LED[13:11] = {SW[11], SW[12], SW[13]}; -assign LED[15:14] = { 2{SW[14]} }; - -endmodule - -``` - -4. Сохранить изменения -5. В окне Sources нажать на кнопку `+` -6. В открывшемся окне выбрать `Add or create constraints` → Нажать Next -7. Нажать `Create File` → В открывшемся окне ввести название → Нажать `OK` → `Finish` -8. В окне `Source` в открывающемся списке `Constraints` найти только что созданный файл и открыть его для редактирования двойным щелчком -9. Скопировать содержимое файла констрейнов с [официального сайта](https://github.com/Digilent/digilent-xdc) и вставить в только что созданный → Найти строки посвященные SW и LED и раскомментировать их → Сохранить изменения -10. `Run Synthesis` -11. `Run Implementation` -12. После успешной имплементации нажимаем `Generate Bitstream` для генерации файла прошивки -13. Аккуратно достаем и подключаем стенд к компьютеру → Включаем питание на плате -14. Нажимаем `Open Hardware Manager` (под `Generate Bitstream`) -15. Вместо окна `Source` будет отображаться окно `Hardware`, в нем необходимо нажать кнопку `Auto Connect` (единственная активная кнопка) → В окне появится подключенное устройство -16. Нажать правой кнопкой на устройстве `xc7a100t_0` → Выбрать пункт меню `Program Device` -17. В открывшемся окне нажать `Program` -18. Сопоставить поведение отладочной платы с SystemVerilog-описанием diff --git a/Other/vector_abs/README.md b/Vivado Basics/vector_abs/README.md similarity index 100% rename from Other/vector_abs/README.md rename to Vivado Basics/vector_abs/README.md diff --git a/Other/vector_abs/half_divider.sv b/Vivado Basics/vector_abs/half_divider.sv similarity index 100% rename from Other/vector_abs/half_divider.sv rename to Vivado Basics/vector_abs/half_divider.sv diff --git a/Other/vector_abs/max_min.sv b/Vivado Basics/vector_abs/max_min.sv similarity index 100% rename from Other/vector_abs/max_min.sv rename to Vivado Basics/vector_abs/max_min.sv diff --git a/Other/vector_abs/tb_vector_abs.sv b/Vivado Basics/vector_abs/tb_vector_abs.sv similarity index 100% rename from Other/vector_abs/tb_vector_abs.sv rename to Vivado Basics/vector_abs/tb_vector_abs.sv diff --git a/Other/vector_abs/vector_abs.sv b/Vivado Basics/vector_abs/vector_abs.sv similarity index 100% rename from Other/vector_abs/vector_abs.sv rename to Vivado Basics/vector_abs/vector_abs.sv