Визуальная электроника

Захотелось вот поупражняться в программировании на SystemVerilog. Какую-то шибко интересную задачу выдумывать не стал — решил просто сделать часы на FPGA. Понятно, что электронные часы являются не слишком интересным устройством. Тем более, что их намного проще сделать на базе микроконтроллера. Однако реализация часов на SystemVerilog позволяет столкнуться с множеством тонкостей данного языка. Понимание этих тонкостей является необходимым для создания более сложных проектов.

В одной из прошлых статей мы создали проект, позволяющий отображать на экране монитора семисегментный индикатор. Тогда в качестве вывода данных для отображения на индикаторе использовался единый вход: input wire [NUM_SEGMENTS*4-1:0]segments. Для упрощения нашего дизайна мы переделаем входные сигналы. Единый вход данных, мы заменим на 6 отдельных входов для каждого отдельного знакоместа:

 

Verilog Code:
  1. module display
  2. (
  3. input wire reset,
  4. input wire clk_video, //74MHz
  5. input wire [NUM_RED_LEDS-1:0]red_leds,
  6. input wire [NUM_GREEN_LEDS-1:0]green_leds,
  7. input wire [4:0]dig1,
  8. input wire [4:0]dig2,
  9. input wire [4:0]dig3,
  10. input wire [4:0]dig4,
  11. input wire [4:0]dig5,
  12. input wire [4:0]dig6,
  13. //VGA output
  14. output wire hsync,
  15. output wire vsync,
  16. output wire [3:0]r,
  17. output wire [3:0]g,
  18. output wire [3:0]b
  19. );

В самом коде мы изменим формирование сигналов для каждого сегмента:

Verilog Code:
  1. always @(posedge clk_video)
  2. begin
  3. seg7_0 <= seg7( dig1 );
  4. seg7_1 <= seg7( dig2 );
  5. seg7_2 <= seg7( dig3 );
  6. seg7_3 <= seg7( dig4 );
  7. seg7_4 <= seg7( dig5 );
  8. seg7_5 <= seg7( dig6 );
  9. end

А также поменяем знакогенератор, добавив кроме символов 0-F еще два символа: пустое место и символ подчеркивания.

Verilog Code:
  1. function [7:0]seg7;
  2. input [4:0]a;
  3. begin
  4. case(a)
  5. 0: seg7 = 63;
  6. 1: seg7 = 6;
  7. 2: seg7 = 91;
  8. 3: seg7 = 79;
  9. 4: seg7 = 102;
  10. 5: seg7 = 109;
  11. 6: seg7 = 125;
  12. 7: seg7 = 7;
  13. 8: seg7 = 127;
  14. 9: seg7 = 111;
  15. 10: seg7 = 119;
  16. 11: seg7 = 124;
  17. 12: seg7 = 57;
  18. 13: seg7 = 94;
  19. 14: seg7 = 121;
  20. 15: seg7 = 113;
  21. 16: seg7 = 0;
  22. 17: seg7 = 8;
  23. endcase
  24. end
  25. endfunction

Как не трудно догадаться 16 это пустое место, а 17 символ подчеркивания.

Секунды часы показывают в двоичном коде с помощью виртуальных светодиодов, также об их изменении свидетельствуют мигающая линия между часами и минутами. Для управления используются две кнопки. Первая позволяет выбирать между часами и минутами. При выборе часов они начинают мигать, а при выборе минут мигают минуты. Вторая кнопка производит инкремент того, что выбрано.

Что же касается кода на SystemVerilog, у меня он получился следующим:

Verilog Code:
  1. module inc_minute(
  2. input logic [3:0] min1,
  3. input logic [3:0] min2,
  4. output logic [3:0] out_min1,
  5. output logic [3:0] out_min2);
  6. assign out_min1 = (min1 == 9) ? 0 : min1 + 1;
  7. assign out_min2 = (min1 == 9) ?
  8. ( (min2 == 5) ? 0 : min2 + 1 )
  9. : min2;
  10. endmodule
  11.  
  12. module inc_hour(
  13. input logic [3:0] hour1,
  14. input logic [3:0] hour2,
  15. output logic [3:0] out_hour1,
  16. output logic [3:0] out_hour2);
  17. assign out_hour1 =
  18. (((hour2 != 2)&&(hour1 == 9)) || ((hour2 == 2)&&(hour1 == 3)))
  19. ? 0 : hour1 + 1;
  20.  
  21. assign out_hour2 =
  22. ((hour2 == 2) && (hour1 == 3))
  23. ? 0 : ( (hour1 == 9) ? hour2 + 1 : hour2 );
  24. endmodule
  25.  
  26. module clock(
  27. input logic clk,
  28. input logic btn_set,
  29. input logic btn_inc,
  30. // the time is displayed like this:
  31. // hour2 hour1 : min2 min1
  32. output logic [3:0] min1 = 0,
  33. output logic [3:0] min2 = 0,
  34. output logic [3:0] hour1 = 0,
  35. output logic [3:0] hour2 = 0,
  36. output logic [5:0] sec=0,
  37. output logic dot);
  38.  
  39. logic [25:0] divider = 0;
  40.  
  41.  
  42. logic [3:0] next_min1;
  43. logic [3:0] next_min2;
  44. logic [3:0] next_hour1;
  45. logic [3:0] next_hour2;
  46.  
  47. logic [1:0] current_set = 0;
  48. logic btn_set_was_pressed = 0;
  49. logic btn_inc_was_pressed = 0;
  50.  
  51. assign dot = sec[0];
  52.  
  53. inc_minute im(min1, min2, next_min1, next_min2);
  54. inc_hour ih(hour1, hour2, next_hour1, next_hour2);
  55.  
  56. always_ff @(posedge clk)
  57. begin
  58.  
  59. if(divider[16:0] == 0)
  60. begin
  61. if(btn_set == 1)
  62. btn_set_was_pressed <= 1;
  63. else
  64. begin
  65. if(btn_set_was_pressed == 1)
  66. current_set <= (current_set == 2)
  67. ? 0 : current_set + 1;
  68.  
  69. btn_set_was_pressed <= 0;
  70. end
  71.  
  72. if(btn_inc == 1)
  73. btn_inc_was_pressed <= 1;
  74. else
  75. begin
  76. if(btn_inc_was_pressed == 1)
  77. begin
  78. if(current_set == 1)
  79. begin
  80. hour1 <= next_hour1;
  81. hour2 <= next_hour2;
  82. sec <= 0;
  83. divider <= 0;
  84. end
  85. else if(current_set == 2)
  86. begin
  87. min1 <= next_min1;
  88. min2 <= next_min2;
  89. sec <= 0;
  90. divider <= 0;
  91. end
  92. end
  93.  
  94. btn_inc_was_pressed <= 0;
  95. end
  96. end
  97.  
  98. // once a second @ 50 MHz oscillator
  99. if(divider == 50000000)
  100. begin
  101. divider <= 0;
  102. sec <= (sec == 59) ? 0 : sec + 1;
  103.  
  104. if(sec == 59)
  105. begin
  106. min1 <= next_min1;
  107. min2 <= next_min2;
  108. if((min1 == 9) && (min2 == 5))
  109. begin
  110. hour1 <= next_hour1;
  111. hour2 <= next_hour2;
  112. end
  113. end
  114. end
  115. else
  116. divider <= divider + 1;
  117. end // always ...
  118.  
  119. endmodule

Не стану утверждать, что получившееся у меня решение является вершиной элегантности. Но, по крайней мере, оно работает, и каких-либо дефектов мне выявить не удалось. Для меня, как новичка в SystemVerilog, это уже большое достижение.

Я не буду подробно разбирать приведенный код. Главным образом, потому что я не настолько хорошо знаю SystemVerilog, чтобы не наврать вам в три короба. Лучше обратитесь к великолепной книге Цифровая схемотехника и архитектура компьютера за авторством Дэвида и Сары Харрис. Она расскажет вам о SystemVerilog намного лучше меня.

Хотелось бы также сказать пару слов о тех самых тонкостях языка, о которых я упомянул в начале. Во-первых, кажется, я наконец-то смог нормально осознать семантику assign и <=. Первый как бы навсегда связывает сигналы функциональной зависимостью. При этом, если меняется один из сигналов, использованных справа от знака равенства, одновременно меняется и сигнал слева. Второй же говорит что-то вроде «присвоить сигналу такое-то значение при событии, указанном в always». При этом все присваивания происходят параллельно, благодаря чему можно успешно наплодить гонок. Для полноты картины стоит отметить, что также существует и блокирующее присваивание =.

Во-вторых, если у вас есть два модуля, имеющих общий выходной сигнал, это наверняка означает ошибку. Однако компилятор не станет ругаться на эту ошибку или каким-то иным образом помогать ее искать. Это обстоятельство может нешуточно усложнить разработку.

В-третьих, как думаете, что произойдет, если вы сделаете в коде опечатку, например, такую?

Verilog Code:
  1. // typo: 'reg' instead of 'seg' !!!
  2. logic hide_reg = (((current_set == 1) && // [... skipped ...]

Программа успешно скомпилируется! Даже несмотря на то, что далее по коду модуль encode_digit получит ставший неинициализированным вход hide_seg. Чтобы не наступать на эти грабли, начинайте код с директивы:

Verilog Code:
  1. `default_nettype none

Наконец, в-четвертых, в старом коде (например, выдаваемом Google) на Verilog’e, предшественнике SystemVerilog, можно увидеть использование типов reg и wire. Не всегда понятно, в чем их отличие друг от друга, а также от logic. На самом деле, все достаточно просто. Если сигнал встречается в always-блоке или в левой части оператора <=, он должен быть объявлен как reg. В остальных случаях он должен быть объявлен, как wire. Поскольку это вносит некоторую путаницу и усложняет изучение языка, как и тот факт, что reg не имеет ничего общего с регистрами процессора, в SystemVerilog был введен новый тип logic, который можно смело использовать вместо reg и wire.

В общем и целом, это был весьма познавательный опыт. Я крайней рекомендую его к повторению, если вы тоже изучаете SystemVerilog. Если хотите, вы даже можете взять за основу полную версию моего кода, доступную на GitHub, и добавить поддержку кнопки декремента. Если же эта задача кажется вам слишком простой, могу предложить добавить в часы функцию будильника.

По традиции видео и исходники:

Скачать: vga_clock.zip

А теперь настала ваша очередь рассказать, как вы учили SystemVerilog (или Verilog/VHDL). Кроме того, если вы знаете, как можно существенно улучшить приведенный мной код, я весь внимание.

Автор: Александр

Источник

 

Добавить комментарий